システムの言語設定(ロケール)を取得する

os プラグインの locale()(権限 os:allow-locale)で OS の言語を、navigator.language で WebView の言語を取得する。2 つの違いと、表示言語の決め方・Intl への渡し方を示す。

システム情報 対象: Tauri 2.x 更新日: 読了目安: 約8分 sys-016
目次
  1. 前提条件
  2. 1. フロントエンドから実装する (TypeScript)
  3. 表示言語を決める
  4. 日付や数値の書式にロケールを明示する
  5. 2. バックエンドから実装する (Rust)
  6. 動作確認
  7. よくあるエラーと対処法
  8. OS ごとの違いと注意点
  9. 関連レシピ

アプリの表示言語を利用者の環境に合わせる、日付や数値を地域の書式で出す、といった多言語対応の最初の一歩は、利用者の言語を知ることです。Tauri アプリでは取り方が 2 つあり、os プラグインの locale() は OS の言語設定を、Web 標準の navigator.language は WebView(ブラウザエンジン)の言語を返します。たいていは同じ言語になりますが、決まり方が別なので一致する保証はありません。どちらを何に使うかと、値の揺れの吸収のしかたを説明します。

前提条件

npm run tauri add os

locale() の権限 os:allow-locale は os:default に含まれるので、tauri add が追加する os:default があれば足ります。権限は呼び出し元のウィンドウごとに判定されるので、サブウィンドウから呼ぶならそのラベルも windows に入れます。navigator.language はプラグインも権限も要りません。Rust で優先言語の一覧を取る場合(2 章)は、src-tauri で cargo add sys-locale も実行します。

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "Capability for the main window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "os:default"
  ]
}

2 つの違いは次のとおりです。

項目locale()(os プラグイン)navigator.language
何の設定かOS の言語設定WebView の言語
取り方非同期(呼ぶたびに Rust 側へ問い合わせる)同期
取れないときnull常に文字列が入る
候補の一覧先頭の 1 つだけnavigator.languages で一覧
引数を省いた Intl の整形使われないWebView 側の言語が使われる
権限os:allow-locale(os:default に含まれる)不要

1. フロントエンドから実装する (TypeScript)

表示言語を決める

表示言語は「アプリ内で利用者が選んだもの → OS の言語 → WebView の言語一覧 → 既定の言語」の順で決めると扱いやすくなります。locale() の値は "ja-JP" のこともあれば "ja" のこともあるので、文字列全体を比べず、Intl.Locale で言語の部分(language)だけを取り出して比べます。Intl.Locale は BCP 47 の形式として解釈できない値で例外を投げるので、try で囲みます。

import { locale } from '@tauri-apps/plugin-os';

const SUPPORTED = ['ja', 'en'] as const;
type Lang = (typeof SUPPORTED)[number];

// "ja-JP" や "ja" を "ja" にそろえ、対応していない言語や不正な値は null にする
function toLang(tag: string | null | undefined): Lang | null {
  if (!tag) return null;
  try {
    const lang = new Intl.Locale(tag).language;
    return (SUPPORTED as readonly string[]).includes(lang) ? (lang as Lang) : null;
  } catch {
    return null; // "C" のように BCP 47 として解釈できない値
  }
}

export async function resolveUiLang(): Promise<Lang> {
  const chosen = toLang(localStorage.getItem('ui-lang')); // アプリ内で選ばれた言語を最優先
  if (chosen) return chosen;
  const fromOs = toLang(await locale()); // os:allow-locale が必要(os:default に含まれる)
  if (fromOs) return fromOs;
  for (const tag of navigator.languages) {
    const lang = toLang(tag);
    if (lang) return lang;
  }
  return 'en';
}

利用者の選択は、Rust 側(メニューなど)からも読めるように Store プラグイン に保存してもかまいません。起動時の引数で言語を上書きしたい場合は、起動時のコマンドライン引数を取得する の --lang の例と組み合わせます。

日付や数値の書式にロケールを明示する

Intl.DateTimeFormat や toLocaleString() の引数を省くと、WebView 側の言語で整形されます。表示言語を OS に合わせたのに日付だけ別の言語になる、というずれを防ぐには、決めたロケールを明示して渡します。

import { locale } from '@tauri-apps/plugin-os';

const osLocale = await locale();
// 引数を省いたときに使われる、WebView 側のロケール
const webviewDefault = new Intl.DateTimeFormat().resolvedOptions().locale;
console.log({ osLocale, navigatorLanguage: navigator.language, languages: navigator.languages, webviewDefault });

export function formatDate(d: Date, tag: string | null): string {
  try {
    return new Intl.DateTimeFormat(tag ?? undefined, { dateStyle: 'full' }).format(d);
  } catch {
    return new Intl.DateTimeFormat(undefined, { dateStyle: 'full' }).format(d); // 不正な値なら既定に戻す
  }
}

console.log(formatDate(new Date(2026, 8, 12), osLocale)); // ja-JP なら「2026年9月12日土曜日」

2. バックエンドから実装する (Rust)

ネイティブのメニューやトレイの文言はページより先に Rust 側で作ることが多いので、その言語は Rust で決めます。tauri_plugin_os::locale() は JS の locale() と同じく先頭の 1 つだけを返します。2 番目以降の候補も見たいときは、sys-locale クレートの get_locales() で優先順の一覧を取ります。メニューの作り方は アプリケーションのメニューバーを作る を参照してください。

use tauri::menu::{MenuBuilder, SubmenuBuilder};

/// OS の優先言語を優先順に返す(例: ["ja-JP", "en-US"])
#[tauri::command]
fn preferred_locales() -> Vec<String> {
    sys_locale::get_locales().collect()
}

fn os_is_japanese() -> bool {
    match tauri_plugin_os::locale() {
        Some(tag) => tag == "ja" || tag.starts_with("ja-"),
        None => false,
    }
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_os::init())
        .setup(|app| {
            // ページより先に作るメニューの文言は Rust 側で言語を決める
            let (file_label, quit_label) = if os_is_japanese() {
                ("ファイル", "終了")
            } else {
                ("File", "Quit")
            };
            let file_menu = SubmenuBuilder::new(app, file_label)
                .quit_with_text(quit_label)
                .build()?;
            let menu = MenuBuilder::new(app).item(&file_menu).build()?;
            app.set_menu(menu)?;
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![preferred_locales])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
import { invoke } from '@tauri-apps/api/core';

const locales = await invoke<string[]>('preferred_locales');
console.log(locales); // 例: ["ja-JP", "en-US"]

動作確認

npm run tauri dev で起動し、2 つ目のコードのログを DevTools のコンソールで確かめます。日本語の Windows では、たとえば次のように出ます。ja-JP と ja のように細かい形が揃わないことがあるのが、言語の部分だけで比べる理由です。

{ osLocale: "ja-JP", navigatorLanguage: "ja", languages: ["ja", "en-US"], webviewDefault: "ja" }
2026年9月12日土曜日

OS の表示言語を英語に変えてサインインし直すと、osLocale と preferred_locales の先頭が en-US などに変わり、resolveUiLang() の結果も en になります(アプリ内で言語を選んでいない場合)。

よくあるエラーと対処法

  • 「os.locale not allowed. Permissions associated with this command: os:allow-locale, os:default」: capabilities に os:default がありません。追加して tauri dev を再起動します。サブウィンドウからだけ「os.locale not allowed on window」で始まるエラーになる場合は、そのラベルを capability の windows に足します。リリースビルドではどちらも「Command plugin:os|locale not allowed by ACL」になります。
  • Intl の呼び出しで RangeError になる: locale() の値が BCP 47 の形式として解釈できません(下記の Linux の C など)。Intl.Locale で確かめ、だめなら既定の言語に戻します。
  • locale() が null を返す: OS から言語を取得できなかったときの値です。navigator.languages と既定の言語で補います。
  • locale() === 'ja' の判定が通らない: 値は "ja-JP" のように地域が付くことがあります。new Intl.Locale(tag).language で言語だけを取り出して比べます。

OS ごとの違いと注意点

  • Windows: locale() は Windows の表示言語を返します。日付や数値の形式を決める「地域」の設定は反映されないので、表示言語は日本語でも地域の形式は別、という利用者もいます。
  • macOS: システム設定の「言語と地域」にある優先する言語の先頭を返します。
  • Linux: 環境変数 LANGUAGE / LC_ALL / LC_MESSAGES / LANG から決まり、ja_JP.UTF-8 は ja-JP に直して返されます。LANG=C の環境では C が返るので、上の toLang() のような検証が要ります。
  • 共通: OS の言語が変わったことを知らせるイベントは os プラグインにありません。起動時に 1 回決めて、途中で変えたい利用者にはアプリ内の設定で切り替えてもらうのが現実的です。

関連レシピ

参考リンク(公式ドキュメント)

Web Ninja

この記事を書いた人

Web Ninja ウェブエンジニア (Web Engineer)

会社員ネットワークエンジニアから独立してかれこれ 25 年以上 Web エンジニアとして活動中。普段は JavaScript と Node.js を自在に操り、時には C++ や Perl といった古流の技も嗜みます。近年は Tauri × Rust という新たな武器を手に、デスクトップアプリ開発の最前線を駆け抜けています。「作りたい」を「作れる」に変えるための、実践的な「技」をお届けします。

お問い合わせ: tauri.ninja@gmail.com

内容の誤り・動かないコードを報告する