はじめに
この記事はElixirアドベントカレンダー2024のシリーズ2、13日目の記事です
端末の言語設定をアプリ起動時に取得して、Phoenix側の言語設定に反映させます
流れ
- [ios]起動
- [ph]page_live.exにアクセス
- [ph]phx-hookでネイティブブリッジ実行
- [ios]言語設定取得
- [ios]コールバック実行
- [ph]言語設定の値をセッションに追加
- [ph]ページを開く際に設定した言語で表示
[ph]page_live.exにアクセス
scope "/", TrarecordWeb do
pipe_through :browser
- get "/", PageController, :index
+ get "/home", PageController, :index
+ live "/", PageLive
end
div要素だけを表示して、マウント時にjs hookのget_localeを実行します
コールバックとして receive
をキーとしてhandle_eventで待ち受けます
receiveイベントはコールバックの値を付けて /homeにリダイレクトしています
defmodule TrarecordWeb.PageLive do
use TrarecordWeb, :live_view
def render(assigns) do
~H"""
<div id="main" phx-hook="NativeBridge"></div>
"""
end
def mount(_params, _session, socket) do
socket
|> push_event("get_locale", %{})
|> then(&{:ok, &1})
end
def handle_event("receive", %{"message" => locale}, socket) do
{:noreply, redirect(socket, to: ~p"/home?locale=#{String.slice(locale, 0..1)}")}
end
end
[ph]phx-hookでネイティブブリッジ実行
UAをチェックいて処理を分けていますが、最近ごちゃごちゃしているので確認が必要です
callbackのトリガーは関数をグローバルオブジェクトとして設定しています
const NativeBridge = {
mounted() {
this.handleEvent("open_safari", ({ url }) => {
try {
window.webkit.messageHandlers.openSafari.postMessage(url);
} catch {}
});
// 以下追加
this.handleEvent("get_locale", () => {
if (window.navigator.userAgent.match(/Mobile/) === null) {
// PC
this.pushEvent("receive", { message: "ja" });
} else if (window.navigator.userAgent.match(/Android/) !== null) {
// Android
this.pushEvent("receive", { message: "ja" });
} else {
// iOS
window.webkit.messageHandlers.getLocale.postMessage("get_locale");
}
});
const callFromNative = (value) => {
this.pushEvent("receive", { message: value });
};
window.callFromNative = callFromNative;
},
};
export default NativeBridge;
[ios]言語設定取得
super.init()
configuration.userContentController.add(self, name: "openSafari")
+ configuration.userContentController.add(self, name: "getLocale")
Locale(identifier: Locale.preferredLanguages.first!))
で優先度が一番上の言語を取得しています
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
switch message.name {
case "openSafari":
print(message.body)
let url = URL(string:message.body as! String)
if( UIApplication.shared.canOpenURL(url!) ) {
UIApplication.shared.open(url!)
}
+ case "getLocale":
+ evalJavaScript(message: Locale(identifier: Locale.preferredLanguages.first!))
case "error":
// You should actually handle the error :)
let error = (message.body as? [String: Any])?["message"] as? String ?? "unknown"
assertionFailure("JavaScript error: \(error)")
default:
assertionFailure("Received invalid message: \(message.name)")
}
}
[ios]コールバック実行
グローバルオブジェクトに追加した関数を取得した言語設定の値を付けて実行します
void in以下はデバッグ用にifを付けてるだけなのでコールバック自体は不要です
// Native -> JS の呼び出し
func evalJavaScript(message: Any) {
let executeScript: String = "window.callFromNative(\"\(message)\");"
webview.evaluateJavaScript(executeScript, completionHandler: { (object, error) -> Void in
if let object = object {
print("ok")
print(object)
}
if let error = error {
print("error")
print(error)
}
})
}
[ph]言語設定の値をセッションに追加
日本語だったらセッションにjaを設定、それ以外は英語を設定してトップページにリダイレクトを行います
defmodule TrarecordWeb.PageController do
use TrarecordWeb, :controller
alias Trarecord.Accounts.User
- def index(conn, _params) do
- redirect_to(conn, conn.assigns.current_user)
- end
+ def index(conn, %{"locale" => "ja"}) do
+ conn
+ |> put_session(:locale, "ja")
+ |> redirect_to(conn.assigns.current_user)
+ end
+ def index(conn, %{"locale" => _locale}) do
+ conn
+ |> put_session(:locale, "en")
+ |> redirect_to(conn.assigns.current_user)
+ end
defp redirect_to(conn, %User{}) do
redirect(conn, to: ~p"/folders")
end
defp redirect_to(conn, nil) do
render(conn, :home, layout: false)
end
end
[ph]ページを開く際に設定した言語で表示
言語設定がセッションに入っているのでmount時にロケールをセットします
共通処理なので、on_mountで行うと良いかもしれません
def mount(_params, session, socket) do
+ Gettext.put_locale(TrarecordWeb.Gettext, Map.get(session, "locale", "ja"))
socket
|> assign(:page, 1)
|> assign(:page_data, Enum.find(intro(), &(&1.page == 1)))
|> then(&{:ok, &1})
end
最後に
JS hook -> WKUserScript
WKUserScript -> global object function
で相互に通信できることがわかりました
ログアウト、ユーザー削除、利用規約などのアプリ全体の設定画面を実装します
本記事は以上になりますありがとうございました