mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
refactor(i18n): key messages by English text; add Russian
Replace the semantic keys with the English source text, so code reads as the
UI does and English needs no catalog: t('Save keys'), tn(count, one, other)
for plurals, msg() for text stored in data, and bare data-i18n* markup that
translates the element's own English. Other languages are one flat JSON each.
- Simplify src/i18n: drop namespaces, the configurable locale pair
(GEV_DEFAULT_LOCALE / GEV_SECONDARY_LOCALE) and its build defines. Locale
comes from ?lang=, the stored choice, then browser languages.
- Add scripts/i18n.mjs (npm run i18n): extracts every message from t/tn/msg
and markup, and fails on missing, unused, placeholder-mismatched or
wrongly-shaped plural entries. npm test runs it via catalog.test.mjs.
- Add Russian (src/i18n/locales/ru.json, 833 messages), started from the
community catalog in #312 and reviewed against the current English.
- Fix data-i18n placed on parents that would wipe their children (cockpit
LEVEL and local weather cells, first-run EXPLORE MANUALLY tile), and a
doubled degree sign in the TRACK ACQUIRED signal.
- Rewrite docs/TRANSLATORS.md for the new workflow; drop sections the earlier
merge resurrected in docs/CURRENT-STATE.md.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
dfbaeafbd8
commit
13efd411b6
112 files changed
+4419
-4856
No files matched your search
+24
-18
@@ -20,7 +20,7 @@ import {
|
||||
* the chip and the dialog are removed outright.
|
||||
*/
|
||||
|
||||
import { t, hasMessage } from './i18n/index.js';
|
||||
import { t, msg, tn } from './i18n/index.js';
|
||||
|
||||
/**
|
||||
* Chip label — pure, exported for tests. The exact English values are pinned
|
||||
@@ -32,8 +32,12 @@ import { t, hasMessage } from './i18n/index.js';
|
||||
export function keySetupChipLabel(status) {
|
||||
const missing = Math.max(0, (status?.total || 0) - (status?.setCount || 0));
|
||||
return missing > 0
|
||||
? t('setup.keySetup.chipWaiting', { count: missing })
|
||||
: t('setup.keySetup.chipReady');
|
||||
? tn(
|
||||
missing,
|
||||
'POWER UP · {count} KEY WAITING',
|
||||
'POWER UP · {count} KEYS WAITING',
|
||||
)
|
||||
: t('POWERED UP');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,10 +190,7 @@ function buildRow(documentRef, key) {
|
||||
|
||||
const unlocks = documentRef.createElement('p');
|
||||
unlocks.className = 'key-setup-unlocks';
|
||||
// The registry `unlocks` text localizes per registry id; an id missing from
|
||||
// the catalog falls back to the server-provided English copy.
|
||||
const unlocksKey = `setup.keySetup.unlocks.${key.id}`;
|
||||
unlocks.textContent = hasMessage(unlocksKey) ? t(unlocksKey) : key.unlocks;
|
||||
unlocks.textContent = t(key.unlocks);
|
||||
|
||||
row.append(head, unlocks);
|
||||
if (!external) {
|
||||
@@ -214,8 +215,10 @@ function buildRow(documentRef, key) {
|
||||
remove.type = 'button';
|
||||
remove.className = 'key-setup-remove';
|
||||
remove.dataset.keySetupRemove = JSON.stringify(key.envVars);
|
||||
remove.textContent = t('setup.keySetup.row.remove');
|
||||
remove.title = t('setup.keySetup.row.removeTitle', { title: key.title });
|
||||
remove.textContent = t('REMOVE');
|
||||
remove.title = t("Remove {title} from this app's saved keys", {
|
||||
title: key.title,
|
||||
});
|
||||
fields.append(remove);
|
||||
}
|
||||
row.append(fields);
|
||||
@@ -380,8 +383,8 @@ export async function initKeySetup({
|
||||
|
||||
const storeLabel = () =>
|
||||
status?.store === 'pinokio-environment'
|
||||
? t('setup.keySetup.store.pinokio')
|
||||
: t('setup.keySetup.store.env');
|
||||
? t('your app configuration')
|
||||
: t('your local .env');
|
||||
|
||||
// doneMessageKey names the whole saved/removed sentence; the storage
|
||||
// destination is interpolated so the {store} phrase stays one spelling.
|
||||
@@ -392,7 +395,7 @@ export async function initKeySetup({
|
||||
)?.set;
|
||||
busy = true;
|
||||
applyButton?.setAttribute('aria-disabled', 'true');
|
||||
say(t('setup.keySetup.status.saving'));
|
||||
say(t('Saving…'));
|
||||
try {
|
||||
const response = await doFetch('/api/setup/keys', {
|
||||
method: 'POST',
|
||||
@@ -406,7 +409,7 @@ export async function initKeySetup({
|
||||
// payload.error is a server-authored message (API value) — shown as-is.
|
||||
say(
|
||||
payload.error ||
|
||||
t('setup.keySetup.status.saveFailed', { status: response.status }),
|
||||
t('Save failed ({status}).', { status: response.status }),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -440,7 +443,7 @@ export async function initKeySetup({
|
||||
say(t(doneMessageKey, { store: storeLabel() }));
|
||||
} catch (error) {
|
||||
say(
|
||||
t('setup.keySetup.status.saveFailedDetail', {
|
||||
t('Save failed: {detail}', {
|
||||
detail: error?.message || error,
|
||||
}),
|
||||
);
|
||||
@@ -460,10 +463,13 @@ export async function initKeySetup({
|
||||
})),
|
||||
);
|
||||
if (!Object.keys(updates).length) {
|
||||
say(t('setup.keySetup.status.pasteFirst'));
|
||||
say(t('Paste at least one key first.'));
|
||||
return;
|
||||
}
|
||||
await submitUpdates(updates, 'setup.keySetup.status.saved');
|
||||
await submitUpdates(
|
||||
updates,
|
||||
msg('Saved to {store}. Restarting — this page reloads itself.'),
|
||||
);
|
||||
};
|
||||
|
||||
chip.addEventListener('click', openDialog);
|
||||
@@ -579,11 +585,11 @@ export async function initKeySetup({
|
||||
// a deliberate two-step the lure cannot pre-satisfy.
|
||||
const ok =
|
||||
typeof globalThis.confirm !== 'function' ||
|
||||
globalThis.confirm(t('setup.keySetup.confirm.remove'));
|
||||
globalThis.confirm(t('Remove this key from your saved configuration?'));
|
||||
if (!ok) return;
|
||||
void submitUpdates(
|
||||
Object.fromEntries(envVars.map((name) => [name, null])),
|
||||
'setup.keySetup.status.removed',
|
||||
msg('Removed from {store}. Restarting — this page reloads itself.'),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user