- Rich sentences (a link or button inside text) can be replaced by data: the inventory gives a rich entry `slots`, a pack value places the site's own elements with {e0}, {e1}, ... (each once, the last may be left out; no markup, plural or other parameters). The engine returns a pieces + element order template; the page inventory carries `rich` slot counts.
- Packs may carry `when: { languages }` (applies only while the site shows one of them; `en` matches `en-GB`) and `rewrite: { words }` (whole-word rewording of the site's own text, messages keep their parameters' values; no sentences of the site in the pack). The hook tells the engine the site's language; a rewording pack makes the delivery carry the whole inventory.
- Apps can ship built-in packs (BuiltInPacks) delivered before installed ones; syncStringPacks hands the tabs a delivery at start only when it changed.
- Translate mode words are a function the app supplies (asked at each draw) and gain a `plural` word; host validates `when` and `rewrite` envelopes.
- Theme doctor: every finding says where (token path or CSS rule), what and how to fix, as catalog keys wb.doctor.*; the Theme Builder shows the full report; the import hint is translated. Doctor words are in the 17 supported catalogs.
- waybetter strings check describes conditions and rewrites.
55 lines
4.1 KiB
TypeScript
55 lines
4.1 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import { test } from 'node:test';
|
|
import { buildDelivery, coveragePercent, packSummary } from '../src/stringPacksModel.ts';
|
|
import { kindForFile, itemState, type StoreItem } from '../src/storeModel.ts';
|
|
import type { PageInventory } from '../../runtime/src/stringPacks.ts';
|
|
|
|
const INVENTORY: PageInventory = { site: 'demo', catalog: 'web', text: 'a0000001 a0000002 a0000003', message: { b0000001: 'count*' }, closed: 'c0000001' };
|
|
const pack = (strings: Record<string, string>, extra: Record<string, unknown> = {}) => JSON.stringify({
|
|
format: 1, site: 'demo', catalog: 'web', language: 'eo', name: 'Esperanto', authors: ['A'], strings, ...extra,
|
|
});
|
|
|
|
test('pack summary: coverage against the inventory, ignored and refused keys, a sample of the pack\'s own text', () => {
|
|
const summary = packSummary(pack({ a0000001: 'Hejmo', a0000002: 'Esplori', b0000001: '{count, plural, one {# afiŝo} other {# afiŝoj}}', zzzzzzzz: 'x', a0000003: '<b>x</b>', c0000001: 'y' }), INVENTORY)!;
|
|
assert.deepEqual(summary, { language: 'eo', translated: 3, translatable: 4, closed: 1, unknown: 1, refused: 2, sample: ['Hejmo', 'Esplori'], otherCatalog: false });
|
|
assert.equal(coveragePercent(summary), '75.0%');
|
|
assert.equal(packSummary('not json', INVENTORY), null);
|
|
assert.equal(packSummary('{"strings": 1}', INVENTORY), null);
|
|
assert.equal(packSummary(pack({ a0000001: 'x' }, { catalog: 'guest' }), INVENTORY)!.otherCatalog, true);
|
|
});
|
|
|
|
test('delivery: the packs that are on, in order, with only the inventory slice their keys need', () => {
|
|
const first = pack({ a0000001: 'Hejmo' });
|
|
const second = pack({ a0000002: 'Esplori', b0000001: '{count}' }, { name: 'Second' });
|
|
const delivery = JSON.parse(buildDelivery([first, second], INVENTORY)!);
|
|
assert.deepEqual(delivery.inventory, { site: 'demo', catalog: 'web', text: 'a0000001 a0000002', message: { b0000001: 'count*' }, closed: '', partial: true });
|
|
assert.deepEqual(delivery.packs.map((p: { name: string }) => p.name), ['Esperanto', 'Second']);
|
|
// Nothing usable: no delivery (the tabs drop theirs).
|
|
assert.equal(buildDelivery([], INVENTORY), null);
|
|
assert.equal(buildDelivery([pack({ zzzzzzzz: 'x' }), 'junk'], INVENTORY), null);
|
|
});
|
|
|
|
test('store model: string packs by file name and by install record', () => {
|
|
assert.equal(kindForFile('eo.strings.json'), 'strings');
|
|
assert.equal(kindForFile('theme.json'), 'theme');
|
|
const item = { kind: 'strings', id: 'esperanto', version: '1.1.0', sha256: 'b'.repeat(64) } as StoreItem;
|
|
const context = { plugins: {}, builtinThemes: [], userThemes: [], records: {} as Record<string, never> };
|
|
assert.deepEqual(itemState(item, context), { state: 'install' });
|
|
const record = { kind: 'strings' as const, source: 'store' as const, version: '1.0.0', sha256: 'a'.repeat(64), installedAt: '' };
|
|
assert.deepEqual(itemState(item, { ...context, records: { 'strings:esperanto': record } as never }), { state: 'update', version: '1.0.0' });
|
|
assert.deepEqual(itemState(item, { ...context, records: { 'strings:esperanto': { ...record, sha256: 'b'.repeat(64) } } as never }), { state: 'installed', version: '1.0.0' });
|
|
});
|
|
|
|
test('delivery: a rewording pack counts as usable and brings the whole inventory; rich keys with slots are translatable', () => {
|
|
const rich: PageInventory = { ...INVENTORY, rich: { d0000001: 2 } };
|
|
const wording = pack({}, { name: 'Wording', language: 'en-x-twitter', when: { languages: ['en'] }, rewrite: { words: { Post: 'Tweet' } } });
|
|
const delivery = JSON.parse(buildDelivery([wording, pack({ a0000001: 'Hejmo' })], rich)!);
|
|
assert.deepEqual(delivery.inventory, rich, 'the whole inventory, not a slice');
|
|
assert.deepEqual(delivery.packs.map((p: { name: string }) => p.name), ['Wording', 'Esperanto']);
|
|
assert.equal(buildDelivery([wording], rich) !== null, true);
|
|
assert.equal(buildDelivery([pack({})], rich), null, 'a pack with neither strings nor a rule changes nothing');
|
|
const summary = packSummary(pack({ d0000001: 'Legu {e1} antaŭ {e0}.', a0000001: 'Hejmo' }), rich)!;
|
|
assert.equal(summary.translatable, 5, 'the rich key with slots is counted as translatable');
|
|
assert.equal(summary.translated, 2);
|
|
assert.equal(summary.refused, 0);
|
|
});
|