mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
i18n: language picker in the bottom-right corner
Replaces the EN/ES/RU buttons in the Visual Presets dock with a picker that scales to many languages: a globe button with the current code next to the POWER UP chip, opening a list of languages named in their own language with their codes, the current one first and checked. A search field appears past 8 languages. Keyboard: arrows, Home/End, Enter/Space, Esc, type-ahead; keys stay in the list while it is open. Choosing a language still asks before dropping unsaved work, then reloads. Phones get the globe alone and a full-width sheet; the layout uses logical properties for right-to-left languages. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
7fd1dd867f
commit
6031c95a81
21 files changed
+1381
-139
No files matched your search
@@ -1,6 +1,6 @@
|
||||
// K-27: the dock language switch reloads the page, so it asks first while the
|
||||
// K-27: the language picker reloads the page, so it asks first while the
|
||||
// reload would lose work (unsaved CCTV calibration, the open cockpit, anything
|
||||
// a module registers).
|
||||
// a module registers). Choosing the language already active does nothing.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { StyleManager } from './applicationShell.js';
|
||||
@@ -11,20 +11,9 @@ import {
|
||||
setLocale,
|
||||
} from '../i18n/index.js';
|
||||
|
||||
function fakeButton() {
|
||||
const listeners = {};
|
||||
return {
|
||||
dataset: {},
|
||||
classList: { toggle() {} },
|
||||
setAttribute() {},
|
||||
addEventListener: (type, listener) => (listeners[type] = listener),
|
||||
click: () => listeners.click(),
|
||||
};
|
||||
}
|
||||
|
||||
/** Render the switch into a fake dock with the given shell state. */
|
||||
/** Wire the shell's language choice with the given shell state. No picker
|
||||
* markup here: the picker's own behaviour is covered by localePicker.test. */
|
||||
function renderSwitch({ calDirty = false, cockpit = false } = {}) {
|
||||
const buttons = [];
|
||||
const previous = {
|
||||
document: globalThis.document,
|
||||
location: globalThis.location,
|
||||
@@ -34,10 +23,7 @@ function renderSwitch({ calDirty = false, cockpit = false } = {}) {
|
||||
const reloads = [];
|
||||
const prompts = [];
|
||||
let answer = true;
|
||||
globalThis.document = {
|
||||
querySelector: () => ({ appendChild: (button) => buttons.push(button) }),
|
||||
createElement: fakeButton,
|
||||
};
|
||||
globalThis.document = { getElementById: () => null };
|
||||
globalThis.location = {
|
||||
href: 'https://gev.example/#v=2',
|
||||
assign() {},
|
||||
@@ -56,12 +42,11 @@ function renderSwitch({ calDirty = false, cockpit = false } = {}) {
|
||||
},
|
||||
cockpitView: { active: cockpit },
|
||||
_confirmLocaleReload: StyleManager.prototype._confirmLocaleReload,
|
||||
_chooseLocale: StyleManager.prototype._chooseLocale,
|
||||
};
|
||||
StyleManager.prototype._initLocaleSelector.call(shell);
|
||||
const button = (locale) =>
|
||||
buttons.find((candidate) => candidate.dataset.locale === locale);
|
||||
return {
|
||||
button,
|
||||
choose: (locale) => shell._chooseLocale(locale),
|
||||
reloads,
|
||||
prompts,
|
||||
answer: (value) => (answer = value),
|
||||
@@ -83,7 +68,7 @@ test.after(() => {
|
||||
test('nothing to lose: the switch reloads without asking', () => {
|
||||
const view = renderSwitch();
|
||||
try {
|
||||
view.button('es').click();
|
||||
view.choose('es');
|
||||
assert.deepEqual(view.prompts, []);
|
||||
assert.equal(view.reloads.length, 1);
|
||||
} finally {
|
||||
@@ -95,13 +80,13 @@ test('unsaved calibration or the open cockpit: the switch asks first', () => {
|
||||
const view = renderSwitch({ calDirty: true, cockpit: true });
|
||||
try {
|
||||
view.answer(false);
|
||||
view.button('ru').click();
|
||||
view.choose('ru');
|
||||
assert.equal(view.prompts.length, 1);
|
||||
assert.match(view.prompts[0], /unsaved CCTV calibration/);
|
||||
assert.match(view.prompts[0], /the open cockpit/);
|
||||
assert.equal(view.reloads.length, 0, 'declined: no reload');
|
||||
view.answer(true);
|
||||
view.button('ru').click();
|
||||
view.choose('ru');
|
||||
assert.equal(view.reloads.length, 1, 'confirmed: reload');
|
||||
} finally {
|
||||
view.restore();
|
||||
@@ -116,7 +101,7 @@ test('modules register their own unsaved work', () => {
|
||||
const view = renderSwitch();
|
||||
try {
|
||||
view.answer(false);
|
||||
view.button('es').click();
|
||||
view.choose('es');
|
||||
assert.match(view.prompts[0], /drawings on the map/);
|
||||
assert.equal(view.reloads.length, 0);
|
||||
} finally {
|
||||
@@ -132,7 +117,7 @@ test('the prompt reads in the active language', () => {
|
||||
const view = renderSwitch({ calDirty: true });
|
||||
try {
|
||||
view.answer(false);
|
||||
view.button('en').click();
|
||||
view.choose('en');
|
||||
assert.equal(view.prompts.length, 1);
|
||||
assert.doesNotMatch(view.prompts[0], /Switching language/);
|
||||
} finally {
|
||||
@@ -140,3 +125,14 @@ test('the prompt reads in the active language', () => {
|
||||
setLocale('en');
|
||||
}
|
||||
});
|
||||
|
||||
test('the language already active neither asks nor reloads', () => {
|
||||
const view = renderSwitch({ calDirty: true });
|
||||
try {
|
||||
assert.equal(view.choose('en'), 'unchanged');
|
||||
assert.deepEqual(view.prompts, []);
|
||||
assert.equal(view.reloads.length, 0);
|
||||
} finally {
|
||||
view.restore();
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user