i18n: add Japanese locale support (#9768)
This commit is contained in:
@@ -43,7 +43,8 @@
|
||||
"test:integration:debug": "DEBUG=1 vitest run --config vitest.integration.config.ts",
|
||||
"i18n:extract": "formatjs extract 'src/**/*.{ts,tsx}' --ignore '**/*.d.ts' --out-file src/i18n/messages/en.json --flatten && pnpm run i18n:compile",
|
||||
"i18n:check": "node scripts/i18n-check.js",
|
||||
"i18n:compile": "node scripts/i18n-compile.js"
|
||||
"i18n:compile": "node scripts/i18n-compile.js",
|
||||
"i18n:validate-ja": "node scripts/i18n-validate-locale.js ja"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aaif/goose-sdk": "workspace:*",
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Validate that a translated locale catalog mirrors en.json and preserves ICU placeholders.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const locale = process.argv[2] || 'ja';
|
||||
const projectDir = path.join(__dirname, '..');
|
||||
const messagesDir = path.join(projectDir, 'src', 'i18n', 'messages');
|
||||
const enPath = path.join(messagesDir, 'en.json');
|
||||
const localePath = path.join(messagesDir, locale + '.json');
|
||||
|
||||
function readJson(file) {
|
||||
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
}
|
||||
|
||||
function extractPlaceholders(message) {
|
||||
const placeholders = new Set();
|
||||
const re = /\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*(?=[},])/g;
|
||||
let match;
|
||||
while ((match = re.exec(message)) !== null) {
|
||||
placeholders.add(match[1]);
|
||||
}
|
||||
return [...placeholders].sort();
|
||||
}
|
||||
|
||||
const en = readJson(enPath);
|
||||
const translated = readJson(localePath);
|
||||
const enKeys = Object.keys(en).sort();
|
||||
const localeKeys = Object.keys(translated).sort();
|
||||
|
||||
const missing = enKeys.filter((key) => !Object.prototype.hasOwnProperty.call(translated, key));
|
||||
const extra = localeKeys.filter((key) => !Object.prototype.hasOwnProperty.call(en, key));
|
||||
const placeholderIssues = [];
|
||||
|
||||
for (const key of enKeys) {
|
||||
if (!translated[key]) continue;
|
||||
const source = en[key].defaultMessage || '';
|
||||
const target = translated[key].defaultMessage || '';
|
||||
const sourcePlaceholders = extractPlaceholders(source);
|
||||
const targetPlaceholders = extractPlaceholders(target);
|
||||
if (JSON.stringify(sourcePlaceholders) !== JSON.stringify(targetPlaceholders)) {
|
||||
placeholderIssues.push({ key, sourcePlaceholders, targetPlaceholders });
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.length || extra.length || placeholderIssues.length) {
|
||||
if (missing.length) console.error('Missing ' + locale + ' keys:', missing.slice(0, 50));
|
||||
if (extra.length) console.error('Extra ' + locale + ' keys:', extra.slice(0, 50));
|
||||
if (placeholderIssues.length) console.error('Placeholder issues:', placeholderIssues.slice(0, 20));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('i18n locale "' + locale + '" is valid (' + enKeys.length + ' messages).');
|
||||
|
||||
@@ -73,6 +73,18 @@ describe('getLocale', () => {
|
||||
expect(getLocale()).toEqual({ locale: 'tr', messageLocale: 'tr' });
|
||||
});
|
||||
|
||||
|
||||
it('supports Japanese from navigator.languages', () => {
|
||||
vi.stubGlobal('navigator', { languages: ['ja-JP'] });
|
||||
expect(getLocale()).toEqual({ locale: 'ja-JP', messageLocale: 'ja' });
|
||||
});
|
||||
|
||||
it('supports POSIX-style Japanese locale from GOOSE_LOCALE', () => {
|
||||
mockAppConfig({ GOOSE_LOCALE: 'ja_JP' });
|
||||
vi.stubGlobal('navigator', { languages: ['xx-XX'] });
|
||||
expect(getLocale()).toEqual({ locale: 'ja-JP', messageLocale: 'ja' });
|
||||
});
|
||||
|
||||
it('supports Hindi from navigator.languages', () => {
|
||||
vi.stubGlobal('navigator', { languages: ['hi-IN'] });
|
||||
expect(getLocale()).toEqual({ locale: 'hi-IN', messageLocale: 'hi' });
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
export { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
/** The set of locales that have translation catalogs. */
|
||||
const SUPPORTED_LOCALES = new Set(['en', 'hi', 'ru', 'tr', 'zh-CN']);
|
||||
const SUPPORTED_LOCALES = new Set(['en', 'hi', 'ru', 'tr', 'zh-CN', 'ja']);
|
||||
|
||||
/**
|
||||
* Map Simplified Chinese aliases (zh, zh-Hans*, zh-SG, zh-MY) to "zh-CN".
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user