i18n(ja): ship Japanese

- Register ja: CATALOGS, LOCALES, LOCALE_NAMES (日本語) and the package
  boundaries; ja and ja-JP tags select it.
- Japanese unit words follow the number directly, as in the catalog:
  formatQuantity writes 5分 and 1時間5分 (km and kt keep the space), and
  formatRelativeTime drops the space CLDR puts in 5 分前.
- Tests: locale tags, picker order, Intl output, durations and the
  ramp/exit/lane wording in Japanese.
- docs/translations/ja.md: style, units, plurals and glossary; ja added to
  TRANSLATORS.md and CURRENT-STATE.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Opus 5.5 committed 2026-10-28 12:59:32 +04:00
1 parent 7ad073aa9c
commit ed40201481
12 files changed
+221 -16

No files matched your search

+4 -4
View File
@@ -40,7 +40,7 @@ test('the current language comes first, the rest by their own names', () => {
nativeNames: LOCALE_NAMES,
}),
),
['ru', 'en', 'es', 'zh-CN', 'zh-Hant'],
['ru', 'en', 'es', 'zh-CN', 'zh-Hant', 'ja'],
);
assert.deepEqual(
codes(
@@ -50,7 +50,7 @@ test('the current language comes first, the rest by their own names', () => {
nativeNames: LOCALE_NAMES,
}),
),
['es', 'en', 'ru', 'zh-CN', 'zh-Hant'],
['es', 'en', 'ru', 'zh-CN', 'zh-Hant', 'ja'],
);
assert.deepEqual(
codes(
@@ -60,7 +60,7 @@ test('the current language comes first, the rest by their own names', () => {
nativeNames: LOCALE_NAMES,
}),
),
['en', 'es', 'ru', 'zh-CN', 'zh-Hant'],
['en', 'es', 'ru', 'zh-CN', 'zh-Hant', 'ja'],
);
const rows = buildLocaleOptions({
locales: MANY,
@@ -263,7 +263,7 @@ test('keyboard model: open on the current language, move, choose, close', () =>
assert.deepEqual(model.keydown({ key: 'ArrowDown' }), { handled: true });
assert.equal(model.activeOption.code, 'en');
model.keydown({ key: 'End' });
assert.equal(model.activeOption.code, 'zh-Hant');
assert.equal(model.activeOption.code, 'ja');
model.keydown({ key: 'Home' });
assert.equal(model.activeOption.code, 'ru');
model.keydown({ key: 'ArrowUp' });
+13 -2
View File
@@ -1,4 +1,4 @@
import { formatNumber, msg, t } from '../i18n/index.js';
import { formatNumber, getLocale, msg, t } from '../i18n/index.js';
// Unit ids stay English in data, params and AI-tool readings; only the label
// shown on the page is translated. dBZ and °C read the same in every language.
@@ -23,6 +23,17 @@ export function unitLabel(unit) {
return Object.hasOwn(UNIT_LABELS, unit) ? t(UNIT_LABELS[unit]) : unit || '';
}
/**
* What goes between a number and its unit label: a no-break space, except
* before a Japanese unit word, which follows the number directly (5分, 1時間).
*/
export function quantitySeparator(label) {
return getLocale() === 'ja' &&
/^[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}]/u.test(label)
? ''
: '\u00a0';
}
/**
* A number and its unit that never break apart: "22,0 км/ч". Readings carry
* no digit grouping ("1008 hPa"), as on weather charts.
@@ -30,7 +41,7 @@ export function unitLabel(unit) {
export function formatQuantity(value, unit, options) {
const label = unitLabel(unit);
const number = formatNumber(value, { useGrouping: false, ...options });
return label ? `${number}\u00a0${label}` : number;
return label ? `${number}${quantitySeparator(label)}${label}` : number;
}
/**
+11
View File
@@ -44,3 +44,14 @@ test('other languages get their decimal separator and no digit grouping', () =>
setLocale('en');
}
});
test('Japanese unit words follow the number directly; symbols keep the space', () => {
try {
setLocale('ja');
assert.equal(formatQuantity(5, 'min'), '5分');
assert.equal(formatQuantity(1, 'h'), '1時間');
assert.equal(formatQuantity(95, 'kt'), '95\u00a0kt');
} finally {
setLocale('en');
}
});