svelte/svelte5手抜き国際化 の履歴(No.3)
更新概要:英語版と日本語版を同時に開発したい†
どうせ個人での開発なのでロケールファイルを切り出したりせず手抜きでやりたい
国際化とは言いつつ、さしあたり日本語版と英語版を作れればいい。
一応、形だけでも多言語への対応の余地を残しておく??
実現したい機能†
- /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 とする
- トップレベルのレイアウトで children を {#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 { JA, EN } from '$lib/translate';
</script>
<EN>An English text!</EN>
<JA>その日本語訳!</JA>
と書いた時にロケールに従ってどちらか一方が表示されるようにする。
src/lib/translate.ts
LANG: ts
export { default as JA } from './components/translate-ja.svelte';
export { default as EN } 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: 1383 (from 2010/06/03),
today: 1,
yesterday: 0