61 lines
3.5 KiB
JavaScript
61 lines
3.5 KiB
JavaScript
import { loadI18n } from '../test-support/i18n.js';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import test from 'node:test';
|
|
|
|
const root = path.resolve(import.meta.dirname, '..');
|
|
|
|
test('translations support placeholders, plurals and German fallback without treating text as HTML', async () => {
|
|
const context = {};
|
|
const i18n = await loadI18n(context);
|
|
assert.equal(i18n.t('app.strecke.wahlen'), 'Strecke wählen');
|
|
assert.equal(i18n.t('media.image.count', { count: 1 }), '1 Bild');
|
|
assert.equal(i18n.t('media.image.count', { count: 2 }), '2 Bilder');
|
|
assert.equal(i18n.t('app.nachste.station', { value1: '<script>', value2: ' · 3 m' }), 'Nächste Station: <script> · 3 m');
|
|
i18n.register('en', { 'app.strecke.wahlen': 'Choose a trail', 'media.image.count': { one: '{count} picture', other: '{count} pictures' } });
|
|
assert.equal(i18n.setLanguage('en-GB', null), 'en');
|
|
assert.equal(i18n.t('app.strecke.wahlen'), 'Choose a trail');
|
|
assert.equal(i18n.t('media.image.count', { count: 0 }), '0 pictures');
|
|
assert.equal(i18n.t('media.audio.count', { count: 1 }), '1 Tonaufnahme');
|
|
assert.equal(i18n.setLanguage('fr', null), 'de');
|
|
assert.throws(() => i18n.t('missing.key'), /Missing translation/);
|
|
});
|
|
|
|
test('language changes translate visible and spoken labels through textContent and retain text markers', async () => {
|
|
const i18n = await loadI18n({});
|
|
const make = key => ({ textContent: '', attributes: new Map(key), getAttribute(name) { return this.attributes.get(name); }, setAttribute(name, value) { this.attributes.set(name, value); } });
|
|
const text = make([['data-i18n', 'app.strecke.wahlen']]);
|
|
const attributes = ['aria-label', 'title', 'placeholder', 'alt'];
|
|
const labels = new Map(attributes.map(attribute => [attribute, make([[`data-i18n-${attribute}`, 'app.strecke.wahlen']])]));
|
|
const root = { documentElement: {}, querySelectorAll(selector) {
|
|
if (selector === '[data-i18n]') return [text];
|
|
for (const [attribute, label] of labels) {
|
|
if (selector === `[data-i18n-${attribute}]`) return [label];
|
|
}
|
|
return [];
|
|
} };
|
|
i18n.apply(root);
|
|
assert.equal(text.textContent, 'Strecke wählen');
|
|
for (const [attribute, label] of labels) assert.equal(label.attributes.get(attribute), 'Strecke wählen');
|
|
i18n.register('en', { 'app.strecke.wahlen': '<Choose a trail>' });
|
|
i18n.setLanguage('en', root);
|
|
assert.equal(text.textContent, '<Choose a trail>');
|
|
for (const [attribute, label] of labels) assert.equal(label.attributes.get(attribute), '<Choose a trail>');
|
|
assert.equal(root.documentElement.lang, 'en');
|
|
assert.equal(text.attributes.get('data-i18n'), 'app.strecke.wahlen');
|
|
});
|
|
|
|
test('every static HTML and JavaScript text key exists in the central catalog and translations load before app modules', async () => {
|
|
const i18n = await loadI18n({});
|
|
const html = await fs.readFile(path.join(root, 'public/index.html'), 'utf8');
|
|
for (const match of html.matchAll(/\bdata-i18n(?:-[\w-]+)?="([^"]+)"/g)) assert.equal(typeof i18n.t(match[1]), 'string', match[1]);
|
|
for (const name of await fs.readdir(path.join(root, 'public/js'))) {
|
|
if (!name.endsWith('.js')) continue;
|
|
const source = await fs.readFile(path.join(root, 'public/js', name), 'utf8');
|
|
for (const match of source.matchAll(/I18n\.t\(["']([^"']+)["']/g)) assert.equal(typeof i18n.t(match[1], { count: 2 }), 'string', match[1]);
|
|
}
|
|
assert.ok(html.indexOf('js/texts-de.js') < html.indexOf('js/i18n.js'));
|
|
assert.ok(html.indexOf('js/i18n.js') < html.indexOf('js/local-routes.js'));
|
|
});
|