Files
mdg-filius/packages/editor/test/i18n.test.ts
T
julian.kiedaischandClaude Opus 5.5 f0e7c73ec7 feat(editor): F4.6c the software installation on a computer's desktop
A new oracle, InstallRun, installs each of Java's programs on new
computers, all of them on one, each installed and removed again, and a
DNS and an e-mail server installed again after their removal; Java saves
the result as a project. A new program is switched off (a computer's
firewall is on), makes the folders and files it lacks (the web server's
start page in FILIUS' language and its picture, /peer2peer,
/mailserver/konten.txt, /dns/hosts), and removing it takes its settings
and leaves its files; a server installed again reads its records or
accounts from them. Java installs in the order of its list and starts
the program at once.

The document's setSoftware does the same, checked against Java's saved
installation. The core knows what a program makes in the files and
installs or removes a program while the simulation runs; the room
watches the document's programs and hands changes to the engine, so a
new command line works at once. The desktop offers Java's dialog with
two lists, arrows, double click and apply changes.

Also: Java's file may name a number it rolled by the result of any
getter (an idref, here a thread's priority); GetterDefaults now
measures the priority of the saved thread classes, which the import
read as 0 before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 15:03:35 +00:00

189 lines
9.7 KiB
TypeScript

// The catalogs: the same keys in both languages, placeholders filled, and —
// where Java FILIUS names a thing — Java's word in its bundle (M11).
import { existsSync, readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, it } from 'vitest';
import { CATALOGS, translate } from '../src/i18n/i18n.js';
const bundles = join(dirname(fileURLToPath(import.meta.url)), '../../../tools/java-oracle/.upstream/src/main/resources/filius/messages');
/** A Java .properties file: ISO-8859-1, \uXXXX escapes, `key = value`, a backslash at a line's end going on in the next without its indent. */
function properties(file: string): Map<string, string> {
const text = new TextDecoder('latin1').decode(readFileSync(join(bundles, file))).replace(/\\\r?\n[ \t]*/g, '');
const entries = new Map<string, string>();
for (const line of text.split(/\r?\n/)) {
const match = /^([\w.]+)\s*=\s*(.*)$/.exec(line);
if (match !== null) entries.set(match[1]!, match[2]!.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex: string) => String.fromCharCode(Number.parseInt(hex, 16))).trim());
}
return entries;
}
/** The editor's keys whose words are Java's, and the key of each in Java's bundle. */
const FROM_JAVA: Readonly<Record<string, string>> = {
'device.pc': 'hw_rechner_msg1',
'device.notebook': 'hw_notebook_msg1',
'device.networkSwitch': 'hw_switch_msg1',
'device.router': 'hw_vermittlungsrechner_msg1',
'device.gateway': 'hw_gateway_msg1',
'device.cable': 'hw_kabel_msg1',
'menu.removeCable': 'guievents_msg5',
'menu.removeAllCables': 'guievents_msg6',
'menu.delete': 'guievents_msg7',
'cable.noFreePort': 'guievents_msg1',
'cable.alreadyConnected': 'guievents_msg12',
'cable.wanOccupied': 'guievents_msg22',
'cable.lanOccupied': 'guievents_msg23',
'cable.wan': 'guievents_msg24',
'cable.lan': 'guievents_msg25',
'cable.wirelessNotPossible': 'guievents_msg26',
'router.title': 'guicontainer_msg2',
'sheet.name': 'jhostkonfiguration_msg1',
'sheet.ipAddress': 'jhostkonfiguration_msg3',
'sheet.subnetMask': 'jhostkonfiguration_msg4',
'sheet.defaultGateway': 'jhostkonfiguration_msg5',
'sheet.dnsServer': 'jhostkonfiguration_msg6',
'sheet.dhcpClient': 'jhostkonfiguration_msg7',
'sheet.macAddress': 'jhostkonfiguration_msg9',
'sheet.ipAsName': 'jhostkonfiguration_msg10',
'sheet.ipForwarding': 'jhostkonfiguration_msg11',
'sheet.macAsName': 'jhostkonfiguration_msg12',
'sheet.wireless': 'jhostkonfiguration_msg13',
'sheet.cable': 'jhostkonfiguration_msg14',
'sheet.chooseNetwork': 'jhostkonfiguration_msg16',
'sheet.cloud': 'jswitchkonfiguration_msg3',
'sheet.switchSsid': 'jswitchkonfiguration_msg4',
'sheet.retention': 'jswitchkonfiguration_msg5',
'sheet.port': 'jmodemkonfiguration_msg4',
'sheet.general': 'jvermittlungsrechnerkonfiguration_msg17',
'sheet.notConnected': 'jvermittlungsrechnerkonfiguration_msg16',
'sheet.rip': 'jvermittlungsrechnerkonfiguration_msg26',
'sheet.forwardingTable': 'jvermittlungsrechnerkonfiguration_msg15',
'sheet.showAllEntries': 'jvermittlungsrechnerkonfiguration_msg11',
'sheet.newEntry': 'jvermittlungsrechnerkonfiguration_msg12',
'route.destinationNetwork': 'jweiterleitungstabelle_msg3',
'route.subnetMask': 'jweiterleitungstabelle_msg4',
'route.gateway': 'jweiterleitungstabelle_msg5',
'route.interfaceIPAddress': 'jweiterleitungstabelle_msg6',
'sheet.dhcpServer': 'jhostkonfiguration_msg8',
'sheet.portForwarding': 'jgatewayconfiguration_msg19',
'dhcp.lowerBound': 'jdhcpkonfiguration_msg1',
'dhcp.upperBound': 'jdhcpkonfiguration_msg2',
'dhcp.subnetMask': 'jdhcpkonfiguration_msg3',
'dhcp.gateway': 'jdhcpkonfiguration_msg4',
'dhcp.dnsServer': 'jdhcpkonfiguration_msg5',
'dhcp.active': 'jdhcpkonfiguration_msg6',
'dhcp.ownSettings': 'jdhcpkonfiguration_msg8',
'dhcp.basic': 'jdhcpkonfiguration_msg10',
'dhcp.static': 'jdhcpkonfiguration_msg11',
'dhcp.macAddress': 'jdhcpkonfiguration_msg12',
'dhcp.ipAddress': 'jdhcpkonfiguration_msg13',
'dhcp.add': 'jdhcpkonfiguration_msg14',
'dhcp.remove': 'jdhcpkonfiguration_msg15',
'forwarding.protocolValue': 'jportforwarding_msg4',
'forwarding.publicPortValue': 'jportforwarding_msg5',
'forwarding.lanIPAddress': 'jportforwarding_msg6',
'forwarding.lanPortValue': 'jportforwarding_msg7',
'sheet.firewall': 'jvermittlungsrechnerkonfiguration_msg4',
'firewall.rules': 'jfirewalldialog_msg21',
'firewall.up': 'jfirewalldialog_msg22',
'firewall.down': 'jfirewalldialog_msg23',
'firewall.add': 'jfirewalldialog_msg24',
'firewall.delete': 'jfirewalldialog_msg25',
'firewall.number': 'jfirewalldialog_msg26',
'firewall.sourceIPAddress': 'jfirewalldialog_msg27',
'firewall.sourceSubnetMask': 'jfirewalldialog_msg28',
'firewall.destinationIPAddress': 'jfirewalldialog_msg29',
'firewall.destinationSubnetMask': 'jfirewalldialog_msg30',
'firewall.protocol': 'jfirewalldialog_msg31',
'firewall.accept': 'jfirewalldialog_msg33',
'firewall.drop': 'jfirewalldialog_msg34',
'firewall.port': 'jfirewalldialog_msg35',
'firewall.active': 'jfirewalldialog_msg38',
'firewall.activeHint': 'jfirewalldialog_msg39',
'firewall.dropIcmp': 'jfirewalldialog_msg40',
'firewall.dropIcmpHint': 'jfirewalldialog_msg41',
'firewall.filterSyn': 'jfirewalldialog_msg42',
'sheet.connections': 'jvermittlungsrechnerkonfiguration_msg23',
'connections.local': 'jvermittlungsrechnerkonfiguration_msg22',
'connections.add': 'jvermittlungsrechnerkonfiguration_msg24',
'connections.most': 'jvermittlungsrechnerkonfiguration_msg25',
'mode.speed': 'guimainmemu_msg16',
'installation.installed': 'installationsdialog_msg3',
'installation.available': 'installationsdialog_msg4',
'installation.apply': 'installationsdialog_msg2',
'mode.currentSpeed': 'guimainmemu_msg15',
'menu.showDesktop': 'guievents_msg3',
'desktop.home': 'desktoppanel_application',
'desktop.installation': 'desktoppanel_msg1',
'network.ipAddress': 'network_msg1',
'network.subnetMask': 'network_msg4',
'network.dnsServer': 'network_msg2',
'network.gateway': 'network_msg3',
'network.macAddress': 'network_msg9',
};
/** Java's words, mended: the prompt says "Vermittlungsrechnerns" (guicontainer_msg1). */
const FROM_JAVA_MENDED: Readonly<Record<string, readonly [string, (java: string) => string]>> = {
'router.prompt': ['guicontainer_msg1', (java) => java.replace(' \\n ', ' ').replace('Vermittlungsrechnerns', 'Vermittlungsrechners')],
// A label above its list, without the colon of Java's label beside it.
'sheet.network': ['jhostkonfiguration_msg15', (java) => java.replace(/:$/, '')],
// Java writes "Eintrag Löschen".
'sheet.deleteEntry': ['jvermittlungsrechnerkonfiguration_msg13', (java) => java.replace('Löschen', 'löschen')],
// Java's word, then what it joins or which card.
'sheet.connectedTo': ['jvermittlungsrechnerkonfiguration_msg6', (java) => `${java} {device}`],
'sheet.nic': ['jvermittlungsrechnerkonfiguration_msg10', (java) => `${java} {number}`],
// A column's title without Java's question mark ("Aktion?").
'firewall.action': ['jfirewalldialog_msg32', (java) => java.replace(/\?$/, '')],
// A label above its list, without the colon.
'firewall.defaultPolicy': ['jfirewalldialog_msg36', (java) => java.replace(/:$/, '')],
// Java's "\n" as line breaks; the last sentence of the German starts with a small letter there.
'firewall.intro': ['jfirewalldialog_msg37', (java) => java.replaceAll('\\n', '\n').replace('\nüber', '\nÜber')],
'firewall.filterSynHint': ['jfirewalldialog_msg43', (java) => java.replaceAll('\\n', '\n')],
// Without Java's key (Strg+D, Strg+R): in a browser they bookmark and reload the page.
'mode.design': ['guimainmemu_msg3', (java) => java.replace(/ \((Strg|Ctrl)\+D\)$/, '')],
'mode.simulation': ['guimainmemu_msg4', (java) => java.replace(/ \((Strg|Ctrl)\+R\)$/, '')],
// Java's lines without their leading space and the empty one; a step starts with a capital letter.
'connections.hint': [
'jvermittlungsrechnerkonfiguration_msg19',
(java) =>
java
.split('\\n')
.map((line) => line.trim().replace(/^(\d\. )(\p{Ll})/u, (_, step: string, letter: string) => step + letter.toUpperCase()))
.filter((line) => line !== '')
.join('\n'),
],
};
describe('the catalogs', () => {
it('have the same keys in German and English, each with a text', () => {
expect(Object.keys(CATALOGS.en).sort()).toEqual(Object.keys(CATALOGS.de).sort());
for (const catalog of Object.values(CATALOGS)) for (const text of Object.values(catalog)) expect(text.trim()).not.toBe('');
});
it('fill placeholders, and show a key they lack', () => {
expect(translate('de', 'zoom.level', { percent: 75 })).toBe('75 %');
expect(translate('en', 'zoom.level')).toBe('{percent} %');
expect(translate('de', 'no.such.key')).toBe('no.such.key');
});
it('name every kind of device', () => {
for (const kind of ['pc', 'notebook', 'networkSwitch', 'router', 'gateway', 'remoteLink', 'unsupported']) expect(CATALOGS.de[`device.${kind}`]).toBeDefined();
// Java's Modem.TYPE, the same in every language.
expect([CATALOGS.de['device.remoteLink'], CATALOGS.en['device.remoteLink']]).toEqual(['Modem', 'Modem']);
});
it.skipIf(!existsSync(bundles))("use Java's words where Java has them (M11)", () => {
const [de, en] = [properties('MessagesBundle_de_DE.properties'), properties('MessagesBundle_en_GB.properties')];
for (const [key, javaKey] of Object.entries(FROM_JAVA)) {
expect(CATALOGS.de[key], key).toBe(de.get(javaKey));
expect(CATALOGS.en[key], key).toBe(en.get(javaKey));
}
for (const [key, [javaKey, mend]] of Object.entries(FROM_JAVA_MENDED)) {
expect(CATALOGS.de[key], key).toBe(mend(de.get(javaKey)!));
expect(CATALOGS.en[key], key).toBe(mend(en.get(javaKey)!));
}
});
});