mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-10 01:09:43 +02:00
The owner's picks after testing the 1.36.0 beta: - Header Stats Style defaults to Compact (two pills with rings) instead of Tiles. The resolver, the pre-paint stamp and the App Settings option all agree; an unknown value now reads as compact. - The Tiles header button is ON by default everywhere but handhelds (their defaults object keeps it off, and the button still needs a 1180px window). The Ctrl+Shift+G gate in tileShortcutFor() now resolves an absent key through the device defaults too, so the chord and the button cannot disagree; before, it required a stored true. - Tab Layout defaults to Classic (the single strip, as before). By state, By case and Ledger stay available as opt-ins. The tile-grid beta the owner used last had only this layout, and it is the one they wanted back. zh-CN option labels follow the new "(default)" markers; docs and wiki updated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
118 lines
5.0 KiB
TypeScript
118 lines
5.0 KiB
TypeScript
/**
|
|
* @fileoverview The Tab Layout settings in Chinese (zh-CN): the App Settings
|
|
* rows #538 added (Tab Layout, State Order, Header Stats Style), their
|
|
* descriptions and every option, run through the real translator over the
|
|
* real index.html in JSDOM.
|
|
*
|
|
* - Each one reads in Chinese with no English left beyond key names and the
|
|
* WS / CPU / MEM readout names, and reads unchanged in English.
|
|
* - The header style "Tiles" is not the tile grid: its option must not take
|
|
* 平铺, the grid's own word, or "Tiles (default)" reads as the grid.
|
|
* - Every new key is in the dictionary once (a repeated key in the object
|
|
* literal silently replaces the first).
|
|
* - The state-row headings the strip draws (CodemanTabTriage's labels)
|
|
* translate too.
|
|
*
|
|
* The rows' `desktop` tag is a generic tag shared with rows older than #538
|
|
* (WebGL Renderer) and is left out of the check.
|
|
*
|
|
* Port: N/A.
|
|
*/
|
|
import { readFileSync } from 'node:fs';
|
|
import { resolve } from 'node:path';
|
|
import vm from 'node:vm';
|
|
import { JSDOM } from 'jsdom';
|
|
import { afterAll, describe, expect, it } from 'vitest';
|
|
|
|
const PUBLIC = resolve(import.meta.dirname, '../src/web/public');
|
|
const read = (f: string) => readFileSync(resolve(PUBLIC, f), 'utf8');
|
|
const I18N = read('i18n.js');
|
|
const INDEX = read('index.html');
|
|
|
|
type Api = { t(s: string): string; configure(o: Record<string, string>): void; start(): void };
|
|
|
|
const ROWS = ['appSettingsTabArrangement', 'appSettingsTabStateOrder', 'appSettingsHeaderStatsStyle'];
|
|
|
|
/** What may stay Latin: key names and the header's readout names. */
|
|
const leftover = (text: string) => text.replace(/\bAlt\+\d\.\.\d\b|\b(WS|CPU|MEM)\b/g, '').match(/[A-Za-z]+/g) ?? [];
|
|
|
|
/** The rows' visible strings: label (without its tag), description, options. */
|
|
function rowStrings(doc: Document): Map<string, string> {
|
|
const out = new Map<string, string>();
|
|
for (const id of ROWS) {
|
|
const select = doc.getElementById(id)!;
|
|
const row = select.closest('.set-row')!;
|
|
const label = row.querySelector('.set-row-label')!.cloneNode(true) as Element;
|
|
label.querySelector('.set-tag')?.remove();
|
|
out.set(`${id} label`, label.textContent!.trim());
|
|
out.set(`${id} description`, row.querySelector('.set-row-desc')!.textContent!.trim());
|
|
for (const option of select.querySelectorAll('option')) {
|
|
out.set(`${id} option ${option.getAttribute('value')}`, option.textContent!.trim());
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
const source = new JSDOM(INDEX);
|
|
const english = rowStrings(source.window.document);
|
|
|
|
const dom = new JSDOM(INDEX, { runScripts: 'outside-only', url: 'http://localhost/' });
|
|
vm.runInContext(I18N, dom.getInternalVMContext(), { filename: 'i18n.js' });
|
|
const api = (dom.window as unknown as { CodemanI18n: Api }).CodemanI18n;
|
|
api.start();
|
|
api.configure({ language: 'zh-CN' });
|
|
const chinese = rowStrings(dom.window.document);
|
|
|
|
afterAll(() => {
|
|
source.window.close();
|
|
dom.window.close();
|
|
});
|
|
|
|
describe('the Tab Layout settings rows in zh-CN', () => {
|
|
it('finds the three rows and all their options (the check is not vacuous)', () => {
|
|
expect(english.size).toBe(15);
|
|
expect(english.get('appSettingsHeaderStatsStyle option compact')).toBe('Compact (default)');
|
|
expect(english.get('appSettingsHeaderStatsStyle option tiles')).toBe('Tiles (label over value)');
|
|
expect(english.get('appSettingsTabStateOrder option urgent-last')).toBe('Needs you at the bottom');
|
|
});
|
|
|
|
it('translates every label, description and option, with no English left', () => {
|
|
const bad: string[] = [];
|
|
for (const [where, text] of chinese) {
|
|
if (text === english.get(where) || leftover(text).length) bad.push(`${where}: "${text}"`);
|
|
}
|
|
expect(bad).toEqual([]);
|
|
});
|
|
|
|
it('keeps the header style Tiles apart from the tile grid (平铺)', () => {
|
|
expect(chinese.get('appSettingsHeaderStatsStyle option tiles')).not.toContain('平铺');
|
|
expect(chinese.get('appSettingsHeaderStatsStyle description')).not.toContain('平铺');
|
|
});
|
|
|
|
it('reads exactly as before in English', () => {
|
|
const en = new JSDOM('<!doctype html><html><body></body></html>', {
|
|
runScripts: 'outside-only',
|
|
url: 'http://localhost/',
|
|
});
|
|
vm.runInContext(I18N, en.getInternalVMContext(), { filename: 'i18n.js' });
|
|
const t = (en.window as unknown as { CodemanI18n: Api }).CodemanI18n;
|
|
t.configure({ language: 'en' });
|
|
expect([...english.values()].filter((s) => t.t(s) !== s)).toEqual([]);
|
|
en.window.close();
|
|
});
|
|
|
|
it('adds each key to the dictionary once', () => {
|
|
const repeated = [...english.values()].filter((s) => {
|
|
const key = s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
return (I18N.match(new RegExp(`^\\s*(?:'${key}'|"${key}"|${key}):`, 'gm')) ?? []).length !== 1;
|
|
});
|
|
expect(repeated).toEqual([]);
|
|
});
|
|
|
|
it('translates the state-row headings the strip draws', () => {
|
|
const labels = [...read('constants.js').matchAll(/\{ key: '\w+', label: '([^']+)'/g)].map((m) => m[1]);
|
|
expect(labels).toEqual(['Needs you', 'Waiting', 'Working', 'Idle']);
|
|
expect(labels.filter((s) => api.t(s) === s || leftover(api.t(s)).length)).toEqual([]);
|
|
});
|
|
});
|