Add Japanese locale (ja) support (#1810)
* Add Japanese locale (ja) support - New: ja.ts with 2624 UI translation keys - New: ja.settings.ts with 1789 settings translation keys - Registered ja in runtime.ts (Locale type, LOCALES, LOCALE_LABEL_KEYS, normalizeLocale) - Added ja-JP BCP-47 mapping in intl.ts - Added JA_MESSAGES in bootstrap.ts - Added ja to lazy-load dictionary map in store.ts - Updated messages.test.ts with ja dictionary - Added common.language.japanese to all 8 existing locale files * fix(i18n): complete Japanese locale updates * test(i18n): enforce locale key parity * docs: add Japanese documentation --------- Co-authored-by: yuchi0531 <yuchi0531@users.noreply.github.com> Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
committed by
GitHub
co-authored by
yuchi0531
Bohdan Triapitsyn
parent
b384deb812
commit
eb051f969e
@@ -57,7 +57,30 @@ const FR_MESSAGES: BootstrapMessages = {
|
||||
loadingData: (providersText, agentsText) => `Chargement des données (${providersText}, ${agentsText})…`,
|
||||
};
|
||||
|
||||
export const getBootstrapMessages = (locale: Locale): BootstrapMessages => (locale === 'fr' ? FR_MESSAGES : EN_MESSAGES);
|
||||
const JA_MESSAGES: BootstrapMessages = {
|
||||
startingApi: 'OpenCode API を起動中…',
|
||||
initializing: '初期化中…',
|
||||
connecting: '接続中…',
|
||||
connected: '接続完了!',
|
||||
connectionError: '接続エラー',
|
||||
disconnected: '切断されました',
|
||||
reconnecting: '再接続中…',
|
||||
initialDataLoadFailed: 'OpenCode に接続しましたが、初期データの読み込みに失敗しました。',
|
||||
cliNotFound: 'OpenCode CLI が見つかりません。先にインストールしてください。',
|
||||
providersReady: '✓ プロバイダ',
|
||||
providersLoading: '… プロバイダ',
|
||||
agentsReady: '✓ エージェント',
|
||||
agentsLoading: '… エージェント',
|
||||
startingDevServer: (hostLabel) => `Webview 開発サーバーを起動中 (${hostLabel})...`,
|
||||
waitingDevServer: (hostLabel, attempt) => `Webview 開発サーバーを待機中 (${hostLabel})... 試行 ${attempt}`,
|
||||
loadingData: (providersText, agentsText) => `データを読み込み中 (${providersText}, ${agentsText})…`,
|
||||
};
|
||||
|
||||
export const getBootstrapMessages = (locale: Locale): BootstrapMessages => {
|
||||
if (locale === 'fr') return FR_MESSAGES;
|
||||
if (locale === 'ja') return JA_MESSAGES;
|
||||
return EN_MESSAGES;
|
||||
};
|
||||
|
||||
export const readStoredLocaleForBootstrap = (): Locale => {
|
||||
if (typeof window === 'undefined') {
|
||||
|
||||
@@ -11,6 +11,7 @@ const INTL_LOCALE_BY_LOCALE: Record<Locale, string> = {
|
||||
'pt-BR': 'pt-BR',
|
||||
ko: 'ko-KR',
|
||||
pl: 'pl-PL',
|
||||
ja: 'ja-JP',
|
||||
};
|
||||
|
||||
const getIntlLocale = (locale: Locale): string => INTL_LOCALE_BY_LOCALE[locale] ?? 'en-US';
|
||||
|
||||
@@ -3,6 +3,7 @@ import { describe, expect, test } from 'bun:test';
|
||||
import { dict as enDict } from './messages/en';
|
||||
import { dict as esDict } from './messages/es';
|
||||
import { dict as frDict } from './messages/fr';
|
||||
import { dict as jaDict } from './messages/ja';
|
||||
import { dict as koDict } from './messages/ko';
|
||||
import { dict as plDict } from './messages/pl';
|
||||
import { dict as ptBrDict } from './messages/pt-BR';
|
||||
@@ -14,6 +15,7 @@ const localeDictionaries = {
|
||||
en: enDict,
|
||||
fr: frDict,
|
||||
es: esDict,
|
||||
ja: jaDict,
|
||||
'pt-BR': ptBrDict,
|
||||
uk: ukDict,
|
||||
ko: koDict,
|
||||
@@ -23,15 +25,18 @@ const localeDictionaries = {
|
||||
} as const;
|
||||
|
||||
describe('i18n dictionaries', () => {
|
||||
test('french stays in key parity with english', () => {
|
||||
test('all locales stay in key parity with english', () => {
|
||||
const englishKeys = Object.keys(enDict).sort();
|
||||
|
||||
expect(Object.keys(frDict).sort()).toEqual(englishKeys);
|
||||
for (const dictionary of Object.values(localeDictionaries)) {
|
||||
expect(Object.keys(dictionary).sort()).toEqual(englishKeys);
|
||||
}
|
||||
});
|
||||
|
||||
test('all locales expose the french language label key', () => {
|
||||
test('all locales expose language label keys', () => {
|
||||
for (const [, dictionary] of Object.entries(localeDictionaries)) {
|
||||
expect(dictionary['common.language.french']).toBeTruthy();
|
||||
expect(dictionary['common.language.japanese']).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ export const dict = {
|
||||
'common.language.brazilianPortuguese': 'Brazilian Portuguese',
|
||||
'common.language.korean': 'Korean',
|
||||
'common.language.polish': 'Polish',
|
||||
'common.language.japanese': 'Japanese',
|
||||
'common.revealPath.finder': 'Reveal in Finder',
|
||||
'common.revealPath.fileExplorer': 'Open in File Explorer',
|
||||
'common.revealPath.fileManager': 'Open in File Manager',
|
||||
|
||||
@@ -14,6 +14,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"common.language.brazilianPortuguese": "Portugués brasileño",
|
||||
"common.language.korean": "Coreano",
|
||||
"common.language.polish": "Polaco",
|
||||
"common.language.japanese": "Japonés",
|
||||
"common.revealPath.finder": "Mostrar en Finder",
|
||||
"common.revealPath.fileExplorer": "Abrir en File Explorer",
|
||||
"common.revealPath.fileManager": "Abrir en gestor de archivos",
|
||||
|
||||
@@ -13,6 +13,7 @@ export const dict = {
|
||||
'common.language.brazilianPortuguese': 'Portugais brésilien',
|
||||
'common.language.korean': 'Coréen',
|
||||
'common.language.polish': 'Polonais',
|
||||
'common.language.japanese': 'Japonais',
|
||||
'common.revealPath.finder': 'Révéler dans le Finder',
|
||||
'common.revealPath.fileExplorer': 'Ouvrir dans l\'explorateur de fichiers',
|
||||
'common.revealPath.fileManager': 'Ouvrir dans le gestionnaire de fichiers',
|
||||
@@ -103,6 +104,7 @@ export const dict = {
|
||||
'sessions.scheduledTasks.editor.scheduleType.daily': 'Tous les jours',
|
||||
'sessions.scheduledTasks.editor.scheduleType.weekly': 'Hebdomadaire',
|
||||
'sessions.scheduledTasks.editor.scheduleType.once': 'Une fois',
|
||||
'sessions.scheduledTasks.editor.scheduleType.cron': 'Cron',
|
||||
'sessions.scheduledTasks.editor.date.label': 'Date',
|
||||
'sessions.scheduledTasks.editor.date.placeholder': 'Sélectionnez une date',
|
||||
'sessions.scheduledTasks.editor.date.previousMonth': 'Mois précédent',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'common.language.brazilianPortuguese': '브라질 포르투갈어',
|
||||
'common.language.korean': '한국어',
|
||||
'common.language.polish': '폴란드어',
|
||||
'common.language.japanese': '일본어',
|
||||
'common.revealPath.finder': 'Finder에서 보기',
|
||||
'common.revealPath.fileExplorer': 'File Explorer에서 열기',
|
||||
'common.revealPath.fileManager': '파일 관리자에서 열기',
|
||||
|
||||
@@ -15,6 +15,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'common.language.brazilianPortuguese': 'Portugalski (Brazylia)',
|
||||
'common.language.korean': 'Koreański',
|
||||
'common.language.polish': 'Polski',
|
||||
'common.language.japanese': 'Japoński',
|
||||
'common.revealPath.finder': 'Pokaż w Finderze',
|
||||
'common.revealPath.fileExplorer': 'Otwórz w Eksploratorze plików',
|
||||
'common.revealPath.fileManager': 'Otwórz w Menedżerze plików',
|
||||
|
||||
@@ -14,6 +14,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"common.language.brazilianPortuguese": "Português brasileiro",
|
||||
"common.language.korean": "Coreano",
|
||||
"common.language.polish": "Polonês",
|
||||
"common.language.japanese": "Japonês",
|
||||
"common.revealPath.finder": "Mostrar no Finder",
|
||||
"common.revealPath.fileExplorer": "Abrir no File Explorer",
|
||||
"common.revealPath.fileManager": "Abrir no gerenciador de arquivos",
|
||||
|
||||
@@ -14,6 +14,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"common.language.brazilianPortuguese": "Бразильська португальська",
|
||||
"common.language.korean": "Корейська",
|
||||
"common.language.polish": "Польська",
|
||||
"common.language.japanese": "Японська",
|
||||
"common.revealPath.finder": "Показати у Finder",
|
||||
"common.revealPath.fileExplorer": "Відкрити у File Explorer",
|
||||
"common.revealPath.fileManager": "Відкрити у файловому менеджері",
|
||||
|
||||
@@ -14,6 +14,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'common.language.brazilianPortuguese': '巴西葡萄牙语',
|
||||
'common.language.korean': '韩语',
|
||||
'common.language.polish': '波兰语',
|
||||
'common.language.japanese': '日语',
|
||||
'common.revealPath.finder': '在 Finder 中显示',
|
||||
'common.revealPath.fileExplorer': '在文件资源管理器中打开',
|
||||
'common.revealPath.fileManager': '在文件管理器中打开',
|
||||
|
||||
@@ -14,6 +14,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'common.language.brazilianPortuguese': '巴西葡萄牙語',
|
||||
'common.language.korean': '韓語',
|
||||
'common.language.polish': '波蘭語',
|
||||
'common.language.japanese': '日語',
|
||||
'common.revealPath.finder': '在 Finder 中顯示',
|
||||
'common.revealPath.fileExplorer': '在檔案總管中開啟',
|
||||
'common.revealPath.fileManager': '在檔案管理員中開啟',
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
export type Locale = 'en' | 'fr' | 'zh-CN' | 'zh-TW' | 'uk' | 'es' | 'pt-BR' | 'ko' | 'pl';
|
||||
export type Locale = 'en' | 'fr' | 'zh-CN' | 'zh-TW' | 'uk' | 'es' | 'pt-BR' | 'ko' | 'pl' | 'ja';
|
||||
|
||||
export const LOCALES = ['en', 'fr', 'zh-CN', 'zh-TW', 'uk', 'es', 'pt-BR', 'ko', 'pl'] as const satisfies readonly Locale[];
|
||||
export const LOCALES = ['en', 'fr', 'zh-CN', 'zh-TW', 'uk', 'es', 'pt-BR', 'ko', 'pl', 'ja'] as const satisfies readonly Locale[];
|
||||
|
||||
export const DEFAULT_LOCALE: Locale = 'en';
|
||||
|
||||
export const LOCALE_LABEL_KEYS: Record<Locale, 'common.language.english' | 'common.language.french' | 'common.language.simplifiedChinese' | 'common.language.traditionalChinese' | 'common.language.ukrainian' | 'common.language.spanish' | 'common.language.brazilianPortuguese' | 'common.language.korean' | 'common.language.polish'> = {
|
||||
export const LOCALE_LABEL_KEYS: Record<Locale, 'common.language.english' | 'common.language.french' | 'common.language.simplifiedChinese' | 'common.language.traditionalChinese' | 'common.language.ukrainian' | 'common.language.spanish' | 'common.language.brazilianPortuguese' | 'common.language.korean' | 'common.language.polish' | 'common.language.japanese'> = {
|
||||
en: 'common.language.english',
|
||||
fr: 'common.language.french',
|
||||
'zh-CN': 'common.language.simplifiedChinese',
|
||||
@@ -14,6 +14,7 @@ export const LOCALE_LABEL_KEYS: Record<Locale, 'common.language.english' | 'comm
|
||||
'pt-BR': 'common.language.brazilianPortuguese',
|
||||
ko: 'common.language.korean',
|
||||
pl: 'common.language.polish',
|
||||
ja: 'common.language.japanese',
|
||||
};
|
||||
|
||||
export const LOCALE_STORAGE_KEY = 'openchamber.i18n.v1';
|
||||
@@ -55,6 +56,9 @@ export function normalizeLocale(value: string | undefined | null): Locale {
|
||||
if (normalized === 'ko' || normalized.startsWith('ko-')) {
|
||||
return 'ko';
|
||||
}
|
||||
if (normalized === 'ja' || normalized.startsWith('ja-')) {
|
||||
return 'ja';
|
||||
}
|
||||
if (normalized === 'pl' || normalized.startsWith('pl-')) {
|
||||
return 'pl';
|
||||
}
|
||||
|
||||
@@ -42,7 +42,9 @@ async function loadDictionary(locale: Locale): Promise<I18nDictionary> {
|
||||
? await import('./messages/ko') as { dict: I18nDictionary }
|
||||
: locale === 'pl'
|
||||
? await import('./messages/pl') as { dict: I18nDictionary }
|
||||
: { dict: enDict };
|
||||
: locale === 'ja'
|
||||
? await import('./messages/ja') as { dict: I18nDictionary }
|
||||
: { dict: enDict };
|
||||
dictionaries.set(locale, mod.dict);
|
||||
return mod.dict;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user