svelte/svelte5手抜き国際化 の履歴(No.2)
更新概要:英語版と日本語版を同時に開発したい†
どうせ個人での開発なのでロケールファイルを切り出したりせず手抜きでやりたい
国際化とは言いつつ、日本語版と英語版を作れればいい
実現したい機能†
- /some/path/to/file ならブラウザの設定を読んで自動で表示言語を選択
- /ja/some/path/to/file や /en/some/path/to/file を読めば指定の言語で表示
- 言語切替ボタン
を実現したい
パスにオプションパラメータ locale を含める†
3つの URL で実質的に同じ内容を表示することになるので1つにまとめたい
- /some/path/to/file
- /ja/some/path/to/file
- /en/some/path/to/file
これには、
src/routes/[[locale=locales]]/some/path/to/file
というパスにファイルを置けばよい。
- [ [locale=locales] ] でオプショナルなパラメータを指定する ← https://learn.svelte.jp/tutorial/optional-params
- lib/params/locales.ts の export const match: ParamMatcher で locale として渡せる文字列を限定する
lib/params/locales.ts
LANG: ts import type { ParamMatcher } from '@sveltejs/kit'; export const locales = ['en', 'ja'] as const; export type Locale = (typeof locales)[number]; // param が locales に含まれるかどうかを返す export const match: ParamMatcher = (param) => { return (locales as readonly string[]).includes(param); };
これで URL で指定したロケールを $page.params.locale として読めるようになった。
LANG: ts import { page } from '$app/stores'; $page.params.locale // returns 'en' | 'ja' | undefined
デフォルトロケールをブラウザ設定から読み取る†
URL でロケールを指定されないときはブラウザ設定から読み取る
import { locale } from 'src/params/locales';
として得られる locale: Writable<"ja" | "en"> が現在のロケール設定になる。
src/params/locales.ts
LANG: ts import { writable } from 'svelte/store'; import { browser } from '$app/environment'; ... // ブラウザ上であれば設定からロケールを読み取る const defaultLocale = (!browser ? 'en' : getDefaultLocale(getLanguagesOnBrowser())); /** * サーバー上では使わない */ export const locale = writable(defaultLocale); /** * 対応するロケールのうちブラウザ設定で最も優先順位の高いものを返す * Accept-Languages を , で split して渡す */ function getDefaultLocale(languages: readonly string[]) { // 対応する言語のうち最も優先順位の高いものを使う return [...languages, locales[0]] // default locale as the last resort .map((s) => s.toLowerCase().replace(/[^A-Za-z].*$/, '')) // 英文字以外が現れたら以降を削除 .find((s) => match(s)) as Locale; } function getLanguagesOnBrowser() { // https://qiita.com/shogo82148/items/548a6c9904eb19269f8c // ブラウザが受け付けている言語リストをブラウザ上で取得 // ここでは window を参照可能 const navigator = window.navigator as { languages?: readonly string[]; language?: string; userLanguage?: string; browserLanguage?: string; }; if (navigator.languages) { return navigator.languages; } const language = navigator.language ?? navigator['userLanguage'] ?? navigator['browserLanguage']; if (language) { return [language]; } else { return []; } }
レイアウトファイルでロケールを設定†
- locale は Writable なので、.svelte ファイル内で値を読む際には $locale とする
- 中身を {#key $locale} で囲むことで、$locale に変更があった際に再レンダリングされるようになる
- URL に locale パラメータが含まれていればそれを locale に設定する
src/routes/[[locale=locales]]/+layout.svelte
LANG: html <script lang="ts"> import '../../app.css'; import { locale, type Locale } from '$params/locales'; import { page } from '$app/stores'; if ($page.params.locale) { locale.set($page.params.locale as Locale); } const { children } = $props(); </script> {#key $locale} {@render children()} {/key}
ロケール限定タグ†
LANG: html <script lang="ts"> import { J, E } from '$lib/translate'; </script> <E>An English text!</E> <J>その日本語訳!</J>
と書いた時にロケールに従ってどちらか一方が表示されるようにする。
src/lib/translate.ts
LANG: ts export { default as J } from './components/translate-ja.svelte'; export { default as E } from './components/translate-en.svelte';
src/lib/components/translate-ja.svelte
LANG: html <script lang="ts"> import { locale } from '$lib'; import type { Snippet } from 'svelte'; let { children }: { children: Snippet } = $props(); </script> {#if $locale == 'ja'}{@render children()}{/if}
src/lib/components/translate-en.svelte
LANG: html <script lang="ts"> import { locale } from '$lib'; import type { Snippet } from 'svelte'; let { children }: { children: Snippet } = $props(); </script> {#if $locale == 'en'}{@render children()}{/if}
文字列単位の翻訳†
LANG: html <script lang="ts"> import { t } from '$lib/translate'; const n = 10; </script> <p>{ t('An English text!<>その日本語訳!') }</p> <p>{ t`Number #{n}<>#{n}番目` }</p>
のように <> で区切って前後に英語と日本語を両方書いておき、 t という関数に渡すとロケール設定に従ってどちらかを表示する。
t とバッククオートでパラメータを埋め込むことも可能。
src/lib/translate.ts に以下を追加
LANG: ts import { locale, read } from '$lib'; export function t(strings: TemplateStringsArray | string, ...args: { toString: () => string }[]) { const raw = typeof strings == 'string' ? [strings] : strings.raw; let joined = raw.length == 0 ? '' : raw[0]; for (let i = 1; i < raw.length; i++) { joined += args[i - 1].toString() + raw[i]; } const splitted = joined.split(/<>/, 2); return read(locale) == 'en' ? splitted[0] : splitted[splitted.length - 1]; }
関数 t は必ず .svelte ファイル内で呼び出すようにする。
言語切替ボタン†
単に /ja あるいは /en 付きのアドレスへ飛べばいい。
これでしばらくやってみよう†
やってみて困ったことがあれば直す。
コメント・質問†
Counter: 498 (from 2010/06/03),
today: 1,
yesterday: 0