mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
i18n: fix review findings and polish ru/es strings
Fixes the 51 P0–P2 findings from the full review of the i18n branch and the issues found while checking every new string on screen in ru and es. - Translate errors and statuses that still reached ru/es in English: feed and tile errors, provider statuses, the ALPR chip, the traffic mode chip, voice plan step labels, fire feed values, receiver bands, scene save reasons and the Cesium attribution link. - Show preset city names in the UI language (src/ui/placeNames.js); data and everything sent to the model stay English. - Share layer names between the layer panel, HUD and voice card (src/ui/layerNames.js). - Keep long ru/es strings whole: voice card error lines, analyst titles, the voice tier status, the Nepal evidence button, the ALPR retry chip. English layout is unchanged. - Ask before a language switch drops unsaved work. - Catalog fixes: glossary terms (пуск, пресет), gender agreement and consistent "peticiones" in es, fewer calques in both languages. - Fix English text that was itself wrong (e.g. "Exit cockpit to fly to a weather", raw tool ids in voice plan steps). Catalogs: es/ru 2535 messages each, complete. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
8029747b7e
commit
00704ddc89
212 files changed
+9871
-909
No files matched your search
+126
-20
@@ -54,6 +54,74 @@ export function collectKeyUpdates(fields) {
|
||||
return updates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Why /api/setup/keys refused a save, by the `code` the server sends with its
|
||||
* English `error` (src/keySetupCore.mjs, server/standalone/key-setup.js). The
|
||||
* English here is the server's own sentence, so the default locale reads the
|
||||
* same as before.
|
||||
*/
|
||||
const SAVE_REFUSALS = Object.freeze({
|
||||
proxied: msg('Provider Settings does not answer proxied requests'),
|
||||
sharing: msg('Provider Settings is disabled while sharing is enabled'),
|
||||
'not-loopback': msg(
|
||||
'Provider Settings answers only the machine running the server',
|
||||
),
|
||||
'not-local-host': msg('Provider Settings answers only local hostnames'),
|
||||
'origin-required': msg('Provider Settings requires an exact local Origin'),
|
||||
'origin-unrecognized': msg('Unrecognized Origin refused'),
|
||||
'cross-origin': msg('Cross-origin requests are refused'),
|
||||
'content-type': msg('Content-Type must be application/json'),
|
||||
method: msg('Method not allowed'),
|
||||
'too-large': msg('Request too large'),
|
||||
'invalid-json': msg('Invalid JSON'),
|
||||
'not-object': msg('Body must be a JSON object of {ENV_VAR: value}'),
|
||||
'no-keys': msg('No keys provided'),
|
||||
'too-many': msg('At most {limit} keys per save'),
|
||||
'unknown-key': msg('Unknown key: {name}'),
|
||||
'not-string': msg('{name} must be a string'),
|
||||
empty: msg('{name} is empty'),
|
||||
'too-long': msg('{name} is longer than any real key ({limit} max)'),
|
||||
'not-printable': msg(
|
||||
'{name} may only contain printable characters with no spaces',
|
||||
),
|
||||
'invalid-char': msg(
|
||||
'{name} contains a character that is not valid in a key (#, quotes, $, \\, or backtick)',
|
||||
),
|
||||
external: msg(
|
||||
'{name} is configured outside Provider Settings and can only be changed where it was set',
|
||||
),
|
||||
'harden-failed': msg(
|
||||
'The key was not saved: could not restrict the credential file to your account; nothing was saved',
|
||||
),
|
||||
'store-unreadable': msg(
|
||||
'The key was not saved: the existing configuration could not be read, so nothing was changed',
|
||||
),
|
||||
'write-failed': msg('Could not write the {store} store'),
|
||||
});
|
||||
|
||||
/**
|
||||
* The status line for a refused save — pure, exported for tests. A known
|
||||
* `code` reads in the active language; anything else shows the server's text.
|
||||
* @param {{code?: string, params?: object, error?: string}} payload
|
||||
* @param {number} status HTTP status of the response.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function saveRefusalText(payload, status) {
|
||||
const pattern = Object.hasOwn(SAVE_REFUSALS, payload?.code)
|
||||
? SAVE_REFUSALS[payload.code]
|
||||
: null;
|
||||
if (pattern) {
|
||||
const values = {};
|
||||
for (const name of ['name', 'limit', 'store']) {
|
||||
const value = payload.params?.[name];
|
||||
if (typeof value === 'string' || typeof value === 'number')
|
||||
values[name] = value;
|
||||
}
|
||||
return t(pattern, values);
|
||||
}
|
||||
return payload?.error || t('Save failed ({status}).', { status });
|
||||
}
|
||||
|
||||
function sleepWithSignal(ms, signal) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (signal?.aborted) {
|
||||
@@ -72,6 +140,36 @@ function sleepWithSignal(ms, signal) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* An OAuth check error. A server-sent reason is kept as-is and later wrapped
|
||||
* in «OAuth check failed: …»; when there is none, the error carries our own
|
||||
* already-translated sentence and is flagged so the toast shows it unwrapped
|
||||
* (the wrapper would only repeat it).
|
||||
* @param {unknown} serverError
|
||||
* @param {string} fallback Already-translated fallback sentence.
|
||||
* @returns {Error}
|
||||
*/
|
||||
function oauthCheckError(serverError, fallback) {
|
||||
if (serverError) return new Error(String(serverError));
|
||||
const error = new Error(fallback);
|
||||
error.oauthFallback = true;
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Status text for a failed OAuth check — pure, exported for tests. A fallback
|
||||
* sentence of our own already says what failed and is shown alone; any other
|
||||
* reason (server text, network error) gets the «OAuth check failed» wrapper.
|
||||
* @param {unknown} error
|
||||
* @returns {string}
|
||||
*/
|
||||
export function oauthCheckFailureText(error) {
|
||||
if (error?.oauthFallback && error.message) return error.message;
|
||||
return t('OAuth check failed: {detail}', {
|
||||
detail: error?.message || error,
|
||||
});
|
||||
}
|
||||
|
||||
export async function waitForChatGptOAuth({
|
||||
fetchImpl,
|
||||
signal,
|
||||
@@ -90,8 +188,9 @@ export async function waitForChatGptOAuth({
|
||||
if (signal?.aborted) return false;
|
||||
if (response.ok && payload.available) return true;
|
||||
if (!response.ok || payload.loginFailed) {
|
||||
throw new Error(
|
||||
payload.error || 'Could not check ChatGPT sign-in. Try again.',
|
||||
throw oauthCheckError(
|
||||
payload.error,
|
||||
t('Could not check ChatGPT sign-in. Try again.'),
|
||||
);
|
||||
}
|
||||
if (now() >= deadline) break;
|
||||
@@ -415,11 +514,7 @@ export async function initKeySetup({
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (disposed) return;
|
||||
if (!response.ok || !payload.ok) {
|
||||
// payload.error is a server-authored message (API value) — shown as-is.
|
||||
say(
|
||||
payload.error ||
|
||||
t('Save failed ({status}).', { status: response.status }),
|
||||
);
|
||||
say(saveRefusalText(payload, response.status));
|
||||
return;
|
||||
}
|
||||
for (const input of root.querySelectorAll('input[data-env-var]'))
|
||||
@@ -496,12 +591,12 @@ export async function initKeySetup({
|
||||
if (current === 'oauth') {
|
||||
writeStoredCloudVoiceAuthMode('api-key');
|
||||
syncCloudVoiceAuth();
|
||||
say('Cloud voice will use OPENAI_API_KEY on the next session.');
|
||||
say(t('Cloud voice will use OPENAI_API_KEY on the next session.'));
|
||||
return;
|
||||
}
|
||||
oauthBusy = true;
|
||||
syncCloudVoiceAuth();
|
||||
say('Checking local ChatGPT OAuth sign-in…');
|
||||
say(t('Checking local ChatGPT OAuth sign-in…'));
|
||||
try {
|
||||
const response = await doFetch('/api/realtime/oauth-status', {
|
||||
cache: 'no-store',
|
||||
@@ -510,22 +605,25 @@ export async function initKeySetup({
|
||||
const payload = await response.json().catch(() => ({}));
|
||||
if (lifetime.signal.aborted) return;
|
||||
if (!response.ok)
|
||||
throw new Error(
|
||||
payload.error || 'Could not check ChatGPT sign-in.',
|
||||
throw oauthCheckError(
|
||||
payload.error,
|
||||
t('Could not check ChatGPT sign-in.'),
|
||||
);
|
||||
if (payload.available) {
|
||||
writeStoredCloudVoiceAuthMode('oauth');
|
||||
syncCloudVoiceAuth();
|
||||
say(
|
||||
'ChatGPT OAuth selected for cloud voice. Your API key stays saved and available.',
|
||||
t(
|
||||
'ChatGPT OAuth selected for cloud voice. Your API key stays saved and available.',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
say(
|
||||
payload.code === 'CODEX_OAUTH_REAUTH_REQUIRED'
|
||||
? 'ChatGPT sign-in expired. Opening sign-in again…'
|
||||
: 'Opening ChatGPT sign-in in your browser…',
|
||||
? t('ChatGPT sign-in expired. Opening sign-in again…')
|
||||
: t('Opening ChatGPT sign-in in your browser…'),
|
||||
);
|
||||
const loginResponse = await doFetch('/api/realtime/oauth-login', {
|
||||
method: 'POST',
|
||||
@@ -538,7 +636,7 @@ export async function initKeySetup({
|
||||
say(
|
||||
loginPayload.error ||
|
||||
payload.error ||
|
||||
'Could not start ChatGPT sign-in on this machine.',
|
||||
t('Could not start ChatGPT sign-in on this machine.'),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -546,13 +644,17 @@ export async function initKeySetup({
|
||||
writeStoredCloudVoiceAuthMode('oauth');
|
||||
syncCloudVoiceAuth();
|
||||
say(
|
||||
'ChatGPT OAuth selected for cloud voice. Your API key stays saved and available.',
|
||||
t(
|
||||
'ChatGPT OAuth selected for cloud voice. Your API key stays saved and available.',
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
say(
|
||||
'Finish ChatGPT sign-in in the browser. Waiting for it to complete…',
|
||||
t(
|
||||
'Finish ChatGPT sign-in in the browser. Waiting for it to complete…',
|
||||
),
|
||||
);
|
||||
const available = await waitForChatGptOAuth({
|
||||
fetchImpl: doFetch,
|
||||
@@ -561,18 +663,22 @@ export async function initKeySetup({
|
||||
if (lifetime.signal.aborted) return;
|
||||
if (!available) {
|
||||
say(
|
||||
'ChatGPT sign-in timed out. Click USE CHATGPT OAUTH to try again.',
|
||||
t('ChatGPT sign-in timed out. Click {button} to try again.', {
|
||||
button: t('USE CHATGPT OAUTH'),
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
writeStoredCloudVoiceAuthMode('oauth');
|
||||
syncCloudVoiceAuth();
|
||||
say(
|
||||
'ChatGPT sign-in complete. OAuth will be used for the next cloud voice session.',
|
||||
t(
|
||||
'ChatGPT sign-in complete. OAuth will be used for the next cloud voice session.',
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
if (lifetime.signal.aborted) return;
|
||||
say(`OAuth check failed: ${error?.message || error}`);
|
||||
say(oauthCheckFailureText(error));
|
||||
} finally {
|
||||
oauthBusy = false;
|
||||
if (!disposed) syncCloudVoiceAuth();
|
||||
|
||||
Reference in new issue
Block a user