6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ElixirAdvent Calendar 2024

Day 14

ElixirDesktopで作るスマホアプリ Part 10 端末の言語設定を取得 ネイティブとの双方向データ送信

Last updated at Posted at 2024-12-27

はじめに

この記事はElixirアドベントカレンダー2024のシリーズ2、13日目の記事です

端末の言語設定をアプリ起動時に取得して、Phoenix側の言語設定に反映させます

流れ

  1. [ios]起動
  2. [ph]page_live.exにアクセス
  3. [ph]phx-hookでネイティブブリッジ実行
  4. [ios]言語設定取得
  5. [ios]コールバック実行
  6. [ph]言語設定の値をセッションに追加
  7. [ph]ページを開く際に設定した言語で表示

[ph]page_live.exにアクセス

lib/trarecord_web/router.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にリダイレクトしています

lib/trarecord_web/live/page_live.ex
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のトリガーは関数をグローバルオブジェクトとして設定しています

assets/js/hooks/NativeBridge.js
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]言語設定取得

native/ios/Trarecord/WebView.swift:L38
        super.init()
        
        configuration.userContentController.add(self, name: "openSafari")
+       configuration.userContentController.add(self, name: "getLocale")

Locale(identifier: Locale.preferredLanguages.first!))
で優先度が一番上の言語を取得しています

native/ios/Trarecord/WebView.swift:L90
    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/ios/Trarecord/WebView.swift:L128
    // 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を設定、それ以外は英語を設定してトップページにリダイレクトを行います

lib/trarecord_web/controllers/page_controller.ex
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で行うと良いかもしれません

lib/trarecord_web/live/onboarding_live/index.ex:L34
  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
で相互に通信できることがわかりました

ログアウト、ユーザー削除、利用規約などのアプリ全体の設定画面を実装します
本記事は以上になりますありがとうございました

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?