アプリの表示言語を利用者の環境に合わせる、日付や数値を地域の書式で出す、といった多言語対応の最初の一歩は、利用者の言語を知ることです。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 回決めて、途中で変えたい利用者にはアプリ内の設定で切り替えてもらうのが現実的です。
