mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
- Translate the remaining 910 strings and review the whole catalog (meaning, terms, punctuation, label length) against the screens. - Register zh-CN: any zh tag maps to it except Traditional ones (Hant, TW, HK, MO), which keep English until a Traditional catalog ships. - Hemisphere letters get their own context so the HUD sector line reads 21.33N, not a compass word; the Bhote Koshi imagery dates follow year-first languages (2021年10月16日). - cjk.css: tight tracking for Chinese text and a 9px floor on the dock voice labels and layer buttons; Latin HUD readouts keep their look. - docs/translations/zh-CN.md: style, plurals and glossary. Co-Authored-By: Claude Opus 5.5 <[email protected]>
96 lines
3.0 KiB
JavaScript
96 lines
3.0 KiB
JavaScript
// Which locale the page runs in: ?lang= → stored choice → browser languages →
|
|
// English. Only shipped locales are ever selected.
|
|
|
|
/** Durable preference key. Matches the repo convention gev:<name>:v1. */
|
|
export const LOCALE_STORAGE_KEY = 'gev:locale:v1';
|
|
|
|
/** Source language and fallback. */
|
|
export const DEFAULT_LOCALE = 'en';
|
|
|
|
/** Shipped locales; order does not matter. Adding one: docs/TRANSLATORS.md. */
|
|
export const LOCALES = Object.freeze(['en', 'es', 'ru', 'zh-CN']);
|
|
|
|
/** Each locale named in its own language, for the selector. */
|
|
export const LOCALE_NAMES = Object.freeze({
|
|
en: 'English',
|
|
es: 'Español',
|
|
ru: 'Русский',
|
|
'zh-CN': '简体中文',
|
|
});
|
|
|
|
/** Right-to-left locales. */
|
|
const RTL_LOCALES = Object.freeze([]);
|
|
|
|
/**
|
|
* Map any language tag to a shipped locale: 'ru-RU' and 'RU' → 'ru',
|
|
* 'zh', 'zh-CN' and 'zh-Hans-SG' → 'zh-CN'.
|
|
* @returns {string|null}
|
|
*/
|
|
export function normalizeLocale(candidate) {
|
|
if (typeof candidate !== 'string') return null;
|
|
const [primary, ...rest] = candidate.trim().toLowerCase().split(/[-_]/);
|
|
// Traditional Chinese readers are not given Simplified: no zh-TW yet.
|
|
if (primary === 'zh')
|
|
return rest.some((part) => ['hant', 'tw', 'hk', 'mo'].includes(part))
|
|
? null
|
|
: 'zh-CN';
|
|
return LOCALES.includes(primary) ? primary : null;
|
|
}
|
|
|
|
function queryLocale(location) {
|
|
try {
|
|
return normalizeLocale(new URLSearchParams(location?.search).get('lang'));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function readStoredLocale(storage) {
|
|
try {
|
|
const store = storage !== undefined ? storage : globalThis.localStorage;
|
|
return normalizeLocale(store?.getItem?.(LOCALE_STORAGE_KEY));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** Store the choice; false when storage is unavailable. */
|
|
export function writeStoredLocale(locale, storage) {
|
|
const normalized = normalizeLocale(locale) || DEFAULT_LOCALE;
|
|
try {
|
|
const store = storage !== undefined ? storage : globalThis.localStorage;
|
|
if (typeof store?.setItem !== 'function') return false;
|
|
store.setItem(LOCALE_STORAGE_KEY, normalized);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** Resolve the boot locale. Every input is injectable for tests. */
|
|
export function resolveLocale({
|
|
location = globalThis.location,
|
|
storage,
|
|
languages = globalThis.navigator?.languages,
|
|
} = {}) {
|
|
const fromQuery = queryLocale(location);
|
|
if (fromQuery) return fromQuery;
|
|
const stored = readStoredLocale(storage);
|
|
if (stored) return stored;
|
|
for (const candidate of Array.isArray(languages) ? languages : []) {
|
|
const normalized = normalizeLocale(candidate);
|
|
if (normalized) return normalized;
|
|
}
|
|
return DEFAULT_LOCALE;
|
|
}
|
|
|
|
/** Set <html lang> and <html dir>. */
|
|
export function applyDocumentLanguage(doc, locale = DEFAULT_LOCALE) {
|
|
const root = doc?.documentElement;
|
|
if (!root) return false;
|
|
const normalized = normalizeLocale(locale) || DEFAULT_LOCALE;
|
|
root.lang = normalized;
|
|
root.dir = RTL_LOCALES.includes(normalized) ? 'rtl' : 'ltr';
|
|
return true;
|
|
}
|