svelte/svelte5手抜き国際化 の履歴(No.3)

更新


プログラミング/svelte

概要:英語版と日本語版を同時に開発したい

どうせ個人での開発なのでロケールファイルを切り出したりせず手抜きでやりたい

国際化とは言いつつ、さしあたり日本語版と英語版を作れればいい。

一応、形だけでも多言語への対応の余地を残しておく??

実現したい機能

  • /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: 231 (from 2010/06/03), today: 7, yesterday: 4