Files
julian.kiedaischandClaude Opus 5.5 b8b999fd66 feat: F4.8d the documentation mode's report as a PDF, as Java's
ReportRun reads Java's report back with iText: every piece of text with its
place, font, size and colour. The editor lays the report out by iText's
rules (A4, leading 1.5, loose chunks in the current line, tables 80 % wide
with padding 2, fixed traffic columns, page breaks) and draws it with
pdf-lib in the PDF's own Helvetica, widths without kerning as iText measures.
All device pages of Internet_Komplett, the first page and the data exchange
of einfaches_rechnernetz_komplett match Java piece by piece; Java's iText
reads our PDF with the same text in the same places. The sidebar gains
Java's "Report"; the frames come from the room by a short subscription.
Not adopted: true/false for DHCP (Java's own Ja/Nein), hash table orders,
an image running off the page, the report writing back into the model.

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

501 lines
25 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',
'explorer.newFolder': 'fileexplorer_msg3',
'explorer.delete': 'fileexplorer_msg4',
'explorer.cut': 'fileexplorer_msg5',
'explorer.copy': 'fileexplorer_msg6',
'explorer.paste': 'fileexplorer_msg7',
'explorer.rename': 'fileexplorer_msg8',
'explorer.newName': 'fileexplorer_msg9',
'explorer.file': 'fileexplorer_msg10',
'explorer.folder': 'fileexplorer_msg11',
'explorer.import': 'fileexplorer_msg2',
'explorer.importTitle': 'fileexplorer_msg12',
'explorer.localFile': 'fileexplorer_msg13',
'explorer.chooseFile': 'fileexplorer_msg14',
'explorer.importButton': 'fileexplorer_msg15',
'explorer.importMissing': 'fileexplorer_msg16',
'explorer.sure': 'fileexplorer_msg18',
'explorer.invalidTarget': 'fileexplorer_msg19',
'explorer.close': 'sw_fileimporter_msg3',
'explorer.imported': 'sw_fileexplorer_msg2',
'explorer.notImported': 'sw_fileexplorer_msg1',
'explorer.tooLarge': 'sw_fileexplorer_msg3',
'viewer.open': 'imageviewer_msg2',
'echoServer.port': 'serverbaustein_msg1',
'echoServer.stop': 'serverbaustein_msg2',
'echoServer.start': 'serverbaustein_msg3',
'simpleClient.address': 'clientbaustein_msg1',
'simpleClient.port': 'clientbaustein_msg2',
'simpleClient.connect': 'clientbaustein_msg3',
'simpleClient.message': 'clientbaustein_msg4',
'simpleClient.send': 'clientbaustein_msg5',
'simpleClient.disconnect': 'clientbaustein_msg6',
'simpleClient.connected': 'sw_clientbaustein_msg2',
'simpleClient.disconnected': 'sw_clientbaustein_msg3',
'simpleClient.notConnected': 'sw_clientbaustein_msg4',
'simpleClient.receiveFailed': 'sw_tcpsocket_msg9',
'simpleClient.failure.unresolved': 'sw_socket_msg1',
'simpleClient.failure.timedOut': 'sw_tcpsocket_msg2',
'simpleClient.failure.failed': 'sw_tcpsocket_msg3',
'dnsServer.start': 'dnsserver_msg1',
'dnsServer.stop': 'dnsserver_msg14',
'dnsServer.tabA': 'dnsserver_msg2',
'dnsServer.tabMX': 'dnsserver_msg3',
'dnsServer.tabNS': 'dnsserver_msg15',
'dnsServer.aName': 'dnsserver_msg4',
'dnsServer.aAddress': 'dnsserver_msg5',
'dnsServer.add': 'dnsserver_msg6',
'dnsServer.remove': 'dnsserver_msg7',
'dnsServer.aNameColumn': 'dnsserver_msg8',
'dnsServer.aAddressColumn': 'dnsserver_msg9',
'dnsServer.mxDomain': 'dnsserver_msg10',
'dnsServer.mxServer': 'dnsserver_msg11',
'dnsServer.mxDomainColumn': 'dnsserver_msg12',
'dnsServer.mxServerColumn': 'dnsserver_msg13',
'dnsServer.nsDomain': 'dnsserver_msg16',
'dnsServer.nsServer': 'dnsserver_msg17',
'dnsServer.nsDomainColumn': 'dnsserver_msg18',
'dnsServer.nsServerColumn': 'dnsserver_msg19',
'dnsServer.recursive': 'dnsserver_msg20',
'textEditor.new': 'texteditor_msg3',
'textEditor.open': 'texteditor_msg4',
'textEditor.save': 'texteditor_msg5',
'textEditor.saveAs': 'texteditor_msg6',
'fileDialog.up': 'dmtnfilechooser_msg1',
'fileDialog.name': 'dmtnfilechooser_msg2',
'fileDialog.cancel': 'dmtnfilechooser_msg4',
'fileDialog.saveTitle': 'dmtnfilechooser_msg5',
'fileDialog.save': 'dmtnfilechooser_msg6',
'fileDialog.openTitle': 'dmtnfilechooser_msg7',
'fileDialog.open': 'dmtnfilechooser_msg8',
'fileDialog.folder': 'dmtnfilechooser_msg11',
'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',
'gnutella.address': 'peertopeeranwendung_msg1',
'gnutella.join': 'peertopeeranwendung_msg2',
'gnutella.joinHint': 'peertopeeranwendung_msg3',
'gnutella.neighbours': 'peertopeeranwendung_msg4',
'gnutella.invalidAddress': 'peertopeeranwendung_msg5',
'gnutella.number': 'peertopeeranwendung_msg6',
'gnutella.addressColumn': 'peertopeeranwendung_msg7',
'gnutella.resultAddress': 'peertopeeranwendung_msg8',
'gnutella.resultName': 'peertopeeranwendung_msg9',
'gnutella.resultSize': 'peertopeeranwendung_msg10',
'gnutella.searchTerm': 'peertopeeranwendung_msg11',
'gnutella.searchHint': 'peertopeeranwendung_msg12',
'gnutella.search': 'peertopeeranwendung_msg13',
'gnutella.stop': 'peertopeeranwendung_msg14',
'gnutella.stopHint': 'peertopeeranwendung_msg15',
'gnutella.clear': 'peertopeeranwendung_msg16',
'gnutella.clearHint': 'peertopeeranwendung_msg17',
'gnutella.download': 'peertopeeranwendung_msg18',
'gnutella.downloadHint': 'peertopeeranwendung_msg19',
'gnutella.searchTab': 'peertopeeranwendung_msg20',
'gnutella.files': 'peertopeeranwendung_msg21',
'gnutella.fileName': 'peertopeeranwendung_msg22',
'gnutella.fileType': 'peertopeeranwendung_msg23',
'gnutella.filesTab': 'peertopeeranwendung_msg24',
'gnutella.maximum': 'peertopeeranwendung_msg25',
'gnutella.save': 'peertopeeranwendung_msg26',
'gnutella.onlyNumbers': 'peertopeeranwendung_msg27',
'gnutella.settingsTab': 'peertopeeranwendung_msg28',
'gnutella.networkTab': 'peertopeeranwendung_msg29',
'gnutella.leave': 'peertopeeranwendung_msg30',
'gnutella.leaveHint': 'peertopeeranwendung_msg31',
'tables.satTitle': 'guievents_msg8',
'tables.mac': 'guievents_msg9',
'tables.port': 'guievents_msg10',
'tables.lastUpdate': 'guievents_msg27',
'tables.satReset': 'guievents_msg28',
'tables.natTitle': 'guievents_msg29',
'tables.protocol': 'guievents_msg30',
'tables.wanAddress': 'guievents_msg31',
'tables.portMapping': 'guievents_msg32',
'tables.lanAddress': 'guievents_msg33',
'tables.lanPort': 'guievents_msg34',
'tables.natReset': 'guievents_msg36',
'tables.portNumber': 'sw_switchfirmware_msg1',
'drop.hold': 'simulation_control_drop_packets',
'personalFirewall.hint': 'firewall_msg2',
'personalFirewall.port': 'firewall_msg3',
'personalFirewall.allSenders': 'firewall_msg4',
'personalFirewall.sameNetwork': 'firewall_msg5',
'personalFirewall.add': 'firewall_msg6',
'personalFirewall.delete': 'firewall_msg7',
'personalFirewall.portColumn': 'firewall_msg8',
'personalFirewall.sender': 'firewall_msg9',
'personalFirewall.setup': 'firewall_msg10',
'personalFirewall.log': 'firewall_msg11',
'personalFirewall.localNetwork': 'firewall_msg12',
'personalFirewall.allowIcmp': 'firewall_msg13',
'personalFirewall.filterUdp': 'firewall_msg14',
'personalFirewall.portRuleRemoved': 'sw_firewall_msg7',
'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',
'mode.documentation': 'guimainmemu_msg14',
'docu.text': 'docusidebar_msg1',
'docu.rectangle': 'docusidebar_msg3',
'docu.fontSize': 'guievents_msg15',
'docu.sizeNormal': 'guievents_msg16',
'docu.sizeLarge': 'guievents_msg17',
'docu.sizeVeryLarge': 'guievents_msg18',
'docu.fontStyle': 'guievents_msg14',
'docu.stylePlain': 'guievents_msg19',
'docu.styleItalic': 'guievents_msg20',
'docu.styleBold': 'guievents_msg21',
'docu.color': 'guievents_msg13',
'docu.export': 'docusidebar_msg5',
'docu.exportHint': 'docusidebar_msg6',
'docu.report': 'docusidebar_msg7',
'docu.reportHint': 'docusidebar_msg8',
'report.title': 'report_title',
'report.overview': 'report_overview',
'report.components': 'report_component_config',
'report.baseConfig': 'report_base_config',
'report.nics': 'report_nics',
'report.apps': 'report_apps',
'report.routingTable': 'report_routing_table',
'report.traffic': 'report_network_traffic',
'report.dnsRecords': 'report_dns_rr',
'report.domain': 'report_domain',
'report.type': 'report_type',
'report.data': 'report_data',
'report.yes': 'main_dlg_YES',
'report.no': 'main_dlg_NO',
'capture.title': 'lauscherdialog_msg1',
'capture.number': 'rp_lauscher_msg1',
'capture.time': 'rp_lauscher_msg2',
'capture.source': 'rp_lauscher_msg3',
'capture.destination': 'rp_lauscher_msg4',
'capture.protocol': 'rp_lauscher_msg5',
'capture.layer': 'rp_lauscher_msg6',
'capture.comment': 'rp_lauscher_msg7',
'capture.networkAccess': 'rp_lauscher_msg8',
'capture.internet': 'rp_lauscher_msg9',
'capture.transport': 'rp_lauscher_msg10',
'capture.application': 'rp_lauscher_msg11',
'capture.protocolWord': 'rp_lauscher_msg12',
'capture.searchForMac': 'rp_lauscher_msg13',
'capture.macIs': 'rp_lauscher_msg14',
'capture.clear': 'nachrichtentabelle_msg7',
'capture.autoscroll': 'nachrichtentabelle_msg8',
'capture.export': 'nachrichtentabelle_msg9',
'capture.closeTab': 'buttontabcomponent_msg1',
'menu.showCapture': 'guievents_msg4',
'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',
'browser.go': 'webbrowser_msg2',
'server.start': 'webserver_msg1',
'server.stop': 'webserver_msg2',
'webServer.virtualHosts': 'webserver_msg3',
'webServer.hostName': 'webserver_msg4',
'webServer.folder': 'webserver_msg5',
'webServer.useVirtualHosts': 'webserver_msg6',
'server.acceptingStopped': 'sw_serveranwendung_msg1',
'server.acceptingStarted': 'sw_serveranwendung_msg2',
'server.serverSocketClosed': 'sw_serveranwendung_msg3',
'server.serverSocketFailed': 'sw_serveranwendung_msg6',
'server.sendFailed.notConnected': 'sw_tcpsocket_msg6',
'server.sendFailed.connectionLost': 'sw_tcpsocket_msg7',
'server.sendFailed.timedOut': 'sw_tcpsocket_msg8', 'emailClient.sender': 'emailanwendung_msg1',
'emailClient.subject': 'emailanwendung_msg2',
'emailClient.recipient': 'emailanwendung_msg3',
'emailClient.inbox': 'emailanwendung_msg5',
'emailClient.sent': 'emailanwendung_msg6',
'emailClient.fetch': 'emailanwendung_msg7',
'emailClient.compose': 'emailanwendung_msg8',
'emailClient.answer': 'emailanwendung_msg9',
'emailClient.deleteHint': 'emailanwendung_msg10',
'emailClient.accountHint': 'emailanwendung_msg11',
'emailClient.composeTitle': 'emailanwendung_msg12',
'emailClient.send': 'emailanwendung_msg13',
'emailClient.toLabel': 'emailanwendung_msg14',
'emailClient.ccLabel': 'emailanwendung_msg15',
'emailClient.bccLabel': 'emailanwendung_msg16',
'emailClient.subjectLabel': 'emailanwendung_msg17',
'emailClient.re': 'emailanwendung_msg18',
'emailClient.wrote': 'emailanwendung_msg19',
'emailClient.invalidAddress': 'emailanwendung_msg20',
'emailClient.noRecipient': 'emailanwendung_msg21',
'emailClient.accountTitle': 'emailanwendung_msg25',
'emailClient.email': 'emailanwendung_msg26',
'emailClient.pop3Host': 'emailanwendung_msg27',
'emailClient.pop3Port': 'emailanwendung_msg28',
'emailClient.smtpHost': 'emailanwendung_msg29',
'emailClient.smtpPort': 'emailanwendung_msg30',
'emailClient.username': 'emailanwendung_msg31',
'emailClient.password': 'emailanwendung_msg32',
'emailClient.save': 'emailanwendung_msg33',
'emailClient.cancel': 'emailanwendung_msg37',
'emailClient.delete': 'emailanwendung_msg43',
'emailClient.account': 'emailanwendung_msg44',
'emailClient.name': 'emailanwendung_msg45',
'emailClient.accountInvalid': 'emailanwendung_msg46',
'emailClient.noAccount': 'emailanwendung_msg47',
'emailServer.start': 'emailserver_msg1',
'emailServer.started': 'emailserver_msg2',
'emailServer.stopped': 'emailserver_msg3',
'emailServer.domain': 'emailserver_msg4',
'emailServer.deleteAccount': 'emailserver_msg8',
'emailServer.yes': 'emailserver_msg9',
'emailServer.cancel': 'emailserver_msg10',
'emailServer.confirmTitle': 'emailserver_msg13',
'emailServer.address': 'emailserver_msg14',
'emailServer.mails': 'emailserver_msg15',
'emailServer.username': 'emailserver_msg16',
'emailServer.name': 'emailserver_msg17',
'emailServer.password': 'emailserver_msg18',
'emailServer.create': 'emailserver_msg19',
'emailServer.lastNamePlaceholder': 'emailserver_msg20',
'emailServer.firstNamePlaceholder': 'emailserver_msg21',
'emailServer.whiteSpace': 'emailserver_msg24',
'emailServer.missing': 'emailserver_msg25',
'emailServer.newAccount': 'emailserver_msg26',
'emailServer.accounts': 'emailserver_msg27',
'emailServer.log': 'emailserver_msg28',
'emailServer.stop': 'emailserver_msg29',
'mail.failure.unresolved': 'sw_socket_msg1',
'mail.failure.timedOut': 'sw_tcpsocket_msg2',
'mail.failure.failed': 'sw_tcpsocket_msg3',
'mail.failure.receiveFailed': 'sw_tcpsocket_msg6',
};
/** Java's words, mended: the prompt says "Vermittlungsrechnerns" (guicontainer_msg1). */
const FROM_JAVA_MENDED: Readonly<Record<string, readonly [string, (java: string) => string]>> = {
// Java's English misspells it.
'docu.saveFailed': ['guimainmemu_msg11', (java) => java.replace('occured', 'occurred')],
'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's message dialog breaks its lines with \n.
'dnsServer.invalidA': ['dnsserver_msg21', (java) => java.replaceAll('\\n', '\n')],
'dnsServer.invalidNS': ['dnsserver_msg22', (java) => java.replaceAll('\\n', '\n')],
'dnsServer.invalidMX': ['dnsserver_msg23', (java) => java.replaceAll('\\n', '\n')],
// 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')],
// Java's welcome without its bold tags (the panel writes it as text, Java's "!" after it).
'emailClient.welcome': ['emailanwendung_msg4', (java) => java.replace(/<\/?b>/g, '')],
// 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'),
],
};
/** Java's words around a value, as its programs join them („Verbindung zu“ + address + „hergestellt“). */
const FROM_JAVA_JOINED: Readonly<Record<string, readonly [string, string, string]>> = {
'server.connectionAccepted': ['sw_serveranwendung_msg4', '{address}', 'sw_serveranwendung_msg5'],
'simpleClient.connectionClosed': ['sw_clientbaustein_msg5', '{address}', 'sw_clientbaustein_msg6'],
'server.connectionClosed': ['sw_servermitarbeiter_msg1', '{address}', 'sw_servermitarbeiter_msg2'],
'emailServer.created': ['emailserver_msg22', '{name}', 'emailserver_msg23'],
'mail.stored': ['sw_smtpmitarbeiter_msg12', '{name}', 'sw_smtpmitarbeiter_msg13'],
'mail.connected': ['sw_smtpclient_msg1', '{address}', 'sw_smtpclient_msg2'],
'mail.disconnected': ['sw_smtpclient_msg1', '{address}', 'sw_smtpclient_msg3'],
};
/** Java's words before a value, joined as its programs join them (SMTPMitarbeiter, SMTPClient, the server window's question). */
const FROM_JAVA_BEFORE: Readonly<Record<string, readonly [string, string, string?]>> = {
'personalFirewall.ruleMatched': ['sw_firewall_msg8', ' #{number} ({rule}) -> {action}'],
'personalFirewall.icmpDropped': ['firewallthread_msg1', ' {sender} -> {receiver} (code: {code}, type: {type})'],
'personalFirewall.portRuleAdded': ['sw_firewall_msg5', ' {port}/{protocol}'],
'mail.sendFailed': ['sw_smtpmitarbeiter_msg14', ' {address}'],
'mail.mailSent': ['sw_smtpclient_msg4', ' {recipients}'],
// "'" + name + "'", the question's two halves written against it.
'emailServer.confirmDelete': ['emailserver_msg11', '{name}', 'emailserver_msg12'],
};
/** Java's page for a server it cannot reach (sw_webbrowser_msg1): its title and its heading. */
const FROM_JAVA_PAGE: Readonly<Record<string, readonly [string, RegExp]>> = {
'browser.unreachableTitle': ['sw_webbrowser_msg1', /<title>(.*)<\/title>/],
'browser.unreachable': ['sw_webbrowser_msg1', /<h1>(.*)<\/h1>/],
};
const templates = join(dirname(fileURLToPath(import.meta.url)), '../../../tools/java-oracle/.upstream/src/main/resources/tmpl');
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)!));
}
for (const [key, [before, value, after]] of Object.entries(FROM_JAVA_JOINED)) {
expect(CATALOGS.de[key], key).toBe(`${de.get(before)} ${value} ${de.get(after)}`);
expect(CATALOGS.en[key], key).toBe(`${en.get(before)} ${value} ${en.get(after)}`);
}
for (const [key, [before, value, after]] of Object.entries(FROM_JAVA_BEFORE)) {
expect(CATALOGS.de[key], key).toBe(`${de.get(before)}${value}${after === undefined ? '' : de.get(after)}`);
expect(CATALOGS.en[key], key).toBe(`${en.get(before)}${value}${after === undefined ? '' : en.get(after)}`);
}
for (const [key, [javaKey, part]] of Object.entries(FROM_JAVA_PAGE)) {
expect(CATALOGS.de[key], key).toBe(part.exec(de.get(javaKey)!)![1]);
expect(CATALOGS.en[key], key).toBe(part.exec(en.get(javaKey)!)![1]);
}
});
it.skipIf(!existsSync(templates))("write the browser's error page as Java's template does (tmpl/http_fehler)", () => {
for (const [language, file] of [['de', 'http_fehler_de_DE.txt'], ['en', 'http_fehler_en_GB.txt']] as const) {
const template = readFileSync(join(templates, file), 'utf8').replaceAll(':meldung:', '{message}').replaceAll(':code:', '{code}');
expect(CATALOGS[language]['browser.errorTitle']).toBe(/<title>(.*)<\/title>/.exec(template)![1]);
expect(CATALOGS[language]['browser.errorText']).toBe(/<p>(.*)<\/p>/.exec(template)![1]!.trim());
expect(/<h1>(.*)<\/h1>/.exec(template)![1]).toBe('{message}');
}
});
});