waybetter 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/README.md +1 -2
  2. package/bin/waybetter.mjs +2 -5
  3. package/cli/buildenv.mjs +1 -1
  4. package/cli/release.mjs +11 -18
  5. package/cli/releasecheck.mjs +15 -0
  6. package/cli/remote.mjs +4 -4
  7. package/cli/store.mjs +80 -28
  8. package/cli/strings.mjs +2 -0
  9. package/dist/runtime/stringPackHook.js +2 -2
  10. package/dist/runtime/stringPackHook.js.map +1 -1
  11. package/dist/runtime/stringPackTranslate.d.ts +3 -1
  12. package/dist/runtime/stringPackTranslate.js +6 -3
  13. package/dist/runtime/stringPackTranslate.js.map +1 -1
  14. package/dist/runtime/stringPacks.d.ts +53 -5
  15. package/dist/runtime/stringPacks.js +161 -25
  16. package/dist/runtime/stringPacks.js.map +1 -1
  17. package/dist/runtime/themeBar.d.ts +28 -0
  18. package/dist/runtime/themeBar.js +82 -0
  19. package/dist/runtime/themeBar.js.map +1 -0
  20. package/dist/runtime/themeComponents.js +16 -3
  21. package/dist/runtime/themeComponents.js.map +1 -1
  22. package/dist/runtime/themeDecor.js +3 -1
  23. package/dist/runtime/themeDecor.js.map +1 -1
  24. package/dist/runtime/themeDerive.d.ts +69 -0
  25. package/dist/runtime/themeDerive.js +117 -0
  26. package/dist/runtime/themeDerive.js.map +1 -0
  27. package/dist/runtime/themeDoctor.d.ts +44 -3
  28. package/dist/runtime/themeDoctor.js +86 -25
  29. package/dist/runtime/themeDoctor.js.map +1 -1
  30. package/dist/runtime/themeGuards.d.ts +60 -0
  31. package/dist/runtime/themeGuards.js +235 -0
  32. package/dist/runtime/themeGuards.js.map +1 -0
  33. package/dist/runtime/themeIcons.d.ts +1 -1
  34. package/dist/runtime/themeIcons.js +13 -5
  35. package/dist/runtime/themeIcons.js.map +1 -1
  36. package/dist/runtime/themeInk.d.ts +37 -0
  37. package/dist/runtime/themeInk.js +111 -0
  38. package/dist/runtime/themeInk.js.map +1 -0
  39. package/dist/runtime/themeInvariants.d.ts +39 -0
  40. package/dist/runtime/themeInvariants.js +273 -0
  41. package/dist/runtime/themeInvariants.js.map +1 -0
  42. package/dist/runtime/themeRuntime.d.ts +15 -1
  43. package/dist/runtime/themeRuntime.js +78 -5
  44. package/dist/runtime/themeRuntime.js.map +1 -1
  45. package/dist/runtime/themeSpec.d.ts +5 -1
  46. package/dist/runtime/themeSpec.js +9 -13
  47. package/dist/runtime/themeSpec.js.map +1 -1
  48. package/dist/shell/InstallDialog.js +14 -8
  49. package/dist/shell/InstallDialog.js.map +1 -1
  50. package/dist/shell/StoreView.d.ts +12 -0
  51. package/dist/shell/StoreView.js +9 -4
  52. package/dist/shell/StoreView.js.map +1 -1
  53. package/dist/shell/frame/AppMenu.d.ts +26 -3
  54. package/dist/shell/frame/AppMenu.js +106 -27
  55. package/dist/shell/frame/AppMenu.js.map +1 -1
  56. package/dist/shell/frame/FindBar.js +1 -1
  57. package/dist/shell/frame/FindBar.js.map +1 -1
  58. package/dist/shell/frame/Shell.d.ts +1 -0
  59. package/dist/shell/frame/Shell.js +10 -2
  60. package/dist/shell/frame/Shell.js.map +1 -1
  61. package/dist/shell/frame/Titlebar.d.ts +2 -1
  62. package/dist/shell/frame/Titlebar.js +5 -3
  63. package/dist/shell/frame/Titlebar.js.map +1 -1
  64. package/dist/shell/frame/WebPanel.js +4 -1
  65. package/dist/shell/frame/WebPanel.js.map +1 -1
  66. package/dist/shell/frame/accountSwitcher.css +10 -10
  67. package/dist/shell/frame/appMenu.css +19 -13
  68. package/dist/shell/frame/boot.js +2 -0
  69. package/dist/shell/frame/boot.js.map +1 -1
  70. package/dist/shell/frame/chromeTheme.d.ts +19 -1
  71. package/dist/shell/frame/chromeTheme.js +51 -14
  72. package/dist/shell/frame/chromeTheme.js.map +1 -1
  73. package/dist/shell/frame/deck.css +30 -30
  74. package/dist/shell/frame/edges.css +159 -0
  75. package/dist/shell/frame/index.d.ts +2 -2
  76. package/dist/shell/frame/index.js +1 -1
  77. package/dist/shell/frame/index.js.map +1 -1
  78. package/dist/shell/frame/mediaModel.d.ts +13 -0
  79. package/dist/shell/frame/mediaModel.js +14 -0
  80. package/dist/shell/frame/mediaModel.js.map +1 -1
  81. package/dist/shell/frame/menuMessages.en.json +43 -1
  82. package/dist/shell/frame/menuModel.d.ts +42 -0
  83. package/dist/shell/frame/menuModel.js +8 -0
  84. package/dist/shell/frame/menuModel.js.map +1 -1
  85. package/dist/shell/frame/nativeMenu.d.ts +5 -0
  86. package/dist/shell/frame/nativeMenu.js +178 -0
  87. package/dist/shell/frame/nativeMenu.js.map +1 -0
  88. package/dist/shell/frame/nativeMenuModel.d.ts +102 -0
  89. package/dist/shell/frame/nativeMenuModel.js +186 -0
  90. package/dist/shell/frame/nativeMenuModel.js.map +1 -0
  91. package/dist/shell/frame/paletteModel.js +3 -1
  92. package/dist/shell/frame/paletteModel.js.map +1 -1
  93. package/dist/shell/frame/paneTheme.css +1 -1
  94. package/dist/shell/frame/reportModel.d.ts +1 -1
  95. package/dist/shell/frame/reportModel.js +1 -1
  96. package/dist/shell/frame/reportModel.js.map +1 -1
  97. package/dist/shell/frame/shell.css +56 -51
  98. package/dist/shell/frame/state.d.ts +2 -0
  99. package/dist/shell/frame/state.js +3 -1
  100. package/dist/shell/frame/state.js.map +1 -1
  101. package/dist/shell/frame/themeStore.js +21 -6
  102. package/dist/shell/frame/themeStore.js.map +1 -1
  103. package/dist/shell/frame/workspaces.css +12 -12
  104. package/dist/shell/installController.d.ts +16 -1
  105. package/dist/shell/installController.js +31 -5
  106. package/dist/shell/installController.js.map +1 -1
  107. package/dist/shell/panel/Appearance.js +12 -3
  108. package/dist/shell/panel/Appearance.js.map +1 -1
  109. package/dist/shell/panel/Settings.d.ts +1 -1
  110. package/dist/shell/panel/Settings.js +4 -3
  111. package/dist/shell/panel/Settings.js.map +1 -1
  112. package/dist/shell/panel/StoreSection.d.ts +2 -1
  113. package/dist/shell/panel/StoreSection.js +31 -1
  114. package/dist/shell/panel/StoreSection.js.map +1 -1
  115. package/dist/shell/panel/StringPacks.d.ts +12 -3
  116. package/dist/shell/panel/StringPacks.js +33 -8
  117. package/dist/shell/panel/StringPacks.js.map +1 -1
  118. package/dist/shell/panel/backup.css +1 -1
  119. package/dist/shell/panel/builder.css +48 -40
  120. package/dist/shell/panel/make.css +8 -7
  121. package/dist/shell/panel/panel.css +401 -401
  122. package/dist/shell/panel/panelMessages.d.ts +41 -1
  123. package/dist/shell/panel/panelMessages.js +13 -1
  124. package/dist/shell/panel/panelMessages.js.map +1 -1
  125. package/dist/shell/panel/plugins.css +5 -5
  126. package/dist/shell/panel/strings.css +1 -1
  127. package/dist/shell/store.css +34 -14
  128. package/dist/shell/storeMessages.d.ts +24 -0
  129. package/dist/shell/storeMessages.js +24 -0
  130. package/dist/shell/storeMessages.js.map +1 -1
  131. package/dist/shell/storeModel.d.ts +31 -7
  132. package/dist/shell/storeModel.js +15 -2
  133. package/dist/shell/storeModel.js.map +1 -1
  134. package/dist/shell/stringPacksModel.js +7 -2
  135. package/dist/shell/stringPacksModel.js.map +1 -1
  136. package/icons.schema.json +9 -0
  137. package/locales/ar.json +109 -2
  138. package/locales/bg.json +109 -2
  139. package/locales/bn.json +109 -2
  140. package/locales/ca.json +109 -2
  141. package/locales/cs.json +109 -2
  142. package/locales/da.json +109 -2
  143. package/locales/de.json +109 -2
  144. package/locales/el.json +109 -2
  145. package/locales/en.json +108 -2
  146. package/locales/es.json +109 -2
  147. package/locales/eu.json +109 -2
  148. package/locales/fa.json +109 -2
  149. package/locales/fi.json +109 -2
  150. package/locales/fil.json +109 -2
  151. package/locales/fr.json +109 -2
  152. package/locales/ga.json +109 -2
  153. package/locales/gl.json +109 -2
  154. package/locales/gu.json +109 -2
  155. package/locales/ha.json +109 -2
  156. package/locales/he.json +109 -2
  157. package/locales/hi.json +109 -2
  158. package/locales/hr.json +109 -2
  159. package/locales/hu.json +110 -2
  160. package/locales/id.json +109 -2
  161. package/locales/ig.json +109 -2
  162. package/locales/it.json +109 -2
  163. package/locales/ja.json +109 -2
  164. package/locales/kn.json +109 -2
  165. package/locales/ko.json +109 -2
  166. package/locales/mr.json +109 -2
  167. package/locales/ms.json +109 -2
  168. package/locales/nb.json +109 -2
  169. package/locales/nl.json +109 -2
  170. package/locales/pl.json +109 -2
  171. package/locales/pt.json +109 -2
  172. package/locales/ro.json +109 -2
  173. package/locales/ru.json +109 -2
  174. package/locales/sk.json +109 -2
  175. package/locales/sr.json +109 -2
  176. package/locales/sv.json +109 -2
  177. package/locales/ta.json +109 -2
  178. package/locales/th.json +109 -2
  179. package/locales/tr.json +109 -2
  180. package/locales/uk.json +109 -2
  181. package/locales/ur.json +109 -2
  182. package/locales/vi.json +109 -2
  183. package/locales/yo.json +109 -2
  184. package/locales/zh-Hans.json +109 -2
  185. package/locales/zh-Hant.json +109 -2
  186. package/package.json +2 -3
  187. package/store-catalog.schema.json +18 -5
  188. package/theme.schema.json +9 -0
  189. package/windows/installer.nsi +22 -2
  190. package/cli/privacy.mjs +0 -768
@@ -1 +1 @@
1
- {"version":3,"file":"stringPackTranslate.js","sourceRoot":"","sources":["../../../runtime/src/stringPackTranslate.ts"],"names":[],"mappings":"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","sourcesContent":["// Translate mode for string packs (published as `waybetter/runtime/stringPackTranslate`): the site's\n// strings carry an invisible marker naming their key, so clicking a word on the page tells which\n// key it is. The author types a replacement (checked with the pack rules right there), the edits\n// collect in the tab as a draft pack that applies on the next reload, and Export copies the pack\n// file. Nothing leaves the page; the draft lives in the tab's session storage.\n//\n// The markers change the site's text (invisible characters at the end of each string), so exact\n// comparisons in the site's own code may notice: translate mode is for authors, never on by default,\n// and leaving it reloads the page without markers.\nimport { compilePack, inventoryLookup, validateString, type InventoryLookup, type PageInventory, type StringPack } from './stringPacks.ts';\n\nconst START = '⁣';\nconst DIGITS = ['​', '‌', '‍', '⁠'];\nconst MARKED = /⁣([​‌‍⁠]+)⁣/g;\n\nexport type Marker = {\n /** The value with its key's marker (strings, and functions whose result is a string). */\n mark(id: string, value: unknown): unknown;\n /** The keys marked in a text, in order. */\n ids(text: string): string[];\n /** The text without markers. */\n strip(text: string): string;\n};\n\nexport function createMarker(): Marker {\n const ids: string[] = [];\n const index = new Map<string, number>();\n const code = (id: string) => {\n let n = index.get(id);\n if (n === undefined) { n = ids.length; ids.push(id); index.set(id, n); }\n let digits = '';\n do { digits = DIGITS[n % 4] + digits; n = Math.floor(n / 4); } while (n);\n return START + digits + START;\n };\n return {\n mark(id, value) {\n if (typeof value === 'string') return value + code(id);\n if (typeof value === 'function') {\n const original = value as (...args: unknown[]) => unknown;\n return function (this: unknown, ...args: unknown[]) {\n const out = original.apply(this, args);\n return typeof out === 'string' ? out + code(id) : out;\n };\n }\n return value;\n },\n ids(text) {\n const out: string[] = [];\n for (const match of text.matchAll(MARKED)) {\n let n = 0;\n for (const ch of match[1]!) n = n * 4 + DIGITS.indexOf(ch);\n const id = ids[n];\n if (id && !out.includes(id)) out.push(id);\n }\n return out;\n },\n strip: text => text.replace(MARKED, ''),\n };\n}\n\n/** The author's edits in this tab. */\nexport type TranslateDraft = { language: string; name: string; strings: Record<string, string> };\n\nexport const DRAFT_KEY = 'wayBetter.translateDraft';\n\nexport function readDraft(storage: Pick<Storage, 'getItem'>): TranslateDraft | null {\n try {\n const value = JSON.parse(storage.getItem(DRAFT_KEY) ?? 'null') as TranslateDraft | null;\n return value && typeof value === 'object' && typeof value.strings === 'object' && value.strings !== null ? value : null;\n } catch {\n return null;\n }\n}\n\n/** The draft as a pack document for `site`/`catalog`. */\nexport function draftPack(draft: TranslateDraft, site: string, catalog: string): StringPack {\n return {\n format: 1, site, catalog, language: draft.language || 'en-x-draft', name: draft.name || 'My strings', authors: ['Me'],\n version: '0.1.0', strings: { ...draft.strings },\n };\n}\n\n/** Checks one edit against the key's shape; `null` when it can be used. */\nexport function checkEdit(lookup: InventoryLookup, id: string, value: string): string | null {\n const shape = lookup(id);\n if (!shape) return 'this key is not in the inventory';\n const result = validateString(shape, value);\n return 'reason' in result ? result.reason : null;\n}\n\nexport type TranslateWords = {\n bar: string; hint: string; edits(count: number): string; exportPack: string; copied: string; exit: string; reload: string;\n key: string; original: string; parameters: string; save: string; cancel: string; remove: string; notTranslatable: string; choose: string;\n};\n\n/** English defaults (the app passes its own words). */\nexport const TRANSLATE_WORDS: TranslateWords = {\n bar: 'Translate mode', hint: 'Click a word to change it.', edits: count => `${count} ${count === 1 ? 'edit' : 'edits'}`,\n exportPack: 'Copy pack file', copied: 'Pack file copied', exit: 'Leave', reload: 'Reload to see it',\n key: 'Key', original: 'Now', parameters: 'Parameters', save: 'Save', cancel: 'Cancel', remove: 'Remove edit',\n notTranslatable: 'This string cannot be changed by a pack yet.', choose: 'Which one?',\n};\n\nexport type TranslateUiOptions = {\n inventory: PageInventory;\n marker: Marker;\n site: string;\n catalog: string;\n words?: Partial<TranslateWords>;\n /** Leaves translate mode (the app drops the flag and reloads). */\n exit(): void;\n};\n\nconst CSS = `\n:host { all: initial; }\n.bar { position: fixed; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 2147483646; display: flex; gap: 10px; align-items: center;\n padding: 8px 12px; border-radius: 12px; background: #111827; color: #f9fafb; font: 13px/1.3 system-ui, sans-serif; box-shadow: 0 8px 24px rgba(0,0,0,.35); }\n.bar b { font-weight: 600; } .bar span { color: #cbd5e1; }\nbutton { font: inherit; border: 0; border-radius: 8px; padding: 6px 10px; cursor: pointer; background: #334155; color: #f9fafb; }\nbutton.primary { background: #2563eb; }\n.pop { position: fixed; z-index: 2147483647; width: 360px; max-width: calc(100vw - 24px); padding: 12px; border-radius: 12px; background: #fff; color: #111827;\n font: 13px/1.4 system-ui, sans-serif; box-shadow: 0 12px 32px rgba(0,0,0,.3); display: grid; gap: 8px; }\n.pop small { color: #475569; } .pop code { font-size: 12px; color: #334155; }\n.pop textarea { width: 100%; box-sizing: border-box; min-height: 64px; font: inherit; padding: 6px; border: 1px solid #cbd5e1; border-radius: 8px; }\n.pop .row { display: flex; gap: 8px; justify-content: flex-end; } .pop .err { color: #b91c1c; }\n.pop .pick { display: grid; gap: 4px; } .pop .pick button { text-align: left; background: #e2e8f0; color: #111827; }\n`;\n\n/**\n * The page part of translate mode: a bar (edits, copy the pack file, leave) and an editor for the\n * string under a click. Clicks on marked text are the editor's while the mode is on.\n */\nexport function installTranslateUi(win: Window, options: TranslateUiOptions): { dispose(): void } {\n const doc = win.document;\n const words = { ...TRANSLATE_WORDS, ...options.words };\n const lookup = inventoryLookup(options.inventory);\n const storage = win.sessionStorage;\n const draft = (): TranslateDraft => readDraft(storage) ?? { language: '', name: '', strings: {} };\n const save = (next: TranslateDraft) => { try { storage.setItem(DRAFT_KEY, JSON.stringify(next)); } catch { /* storage full or blocked */ } };\n\n const host = doc.createElement('wb-translate');\n const root = host.attachShadow({ mode: 'open' });\n const style = doc.createElement('style');\n style.textContent = CSS;\n root.append(style);\n const bar = doc.createElement('div');\n bar.className = 'bar';\n root.append(bar);\n let pop: HTMLElement | null = null;\n let changed = false;\n\n const button = (label: string, onClick: () => void, primary = false) => {\n const el = doc.createElement('button');\n el.textContent = label;\n if (primary) el.className = 'primary';\n el.addEventListener('click', event => { event.preventDefault(); event.stopPropagation(); onClick(); });\n return el;\n };\n const renderBar = () => {\n bar.replaceChildren();\n const title = doc.createElement('b');\n title.textContent = words.bar;\n const hint = doc.createElement('span');\n hint.textContent = `${words.hint} · ${words.edits(Object.keys(draft().strings).length)}`;\n bar.append(title, hint);\n if (changed) bar.append(button(words.reload, () => win.location.reload(), true));\n bar.append(button(words.exportPack, () => {\n const text = JSON.stringify(draftPack(draft(), options.site, options.catalog), null, 2);\n void win.navigator.clipboard?.writeText(text).then(() => { hint.textContent = words.copied; }).catch(() => {});\n }));\n bar.append(button(words.exit, options.exit));\n };\n const closePop = () => { pop?.remove(); pop = null; };\n\n const edit = (id: string, x: number, y: number, now: string) => {\n closePop();\n pop = doc.createElement('div');\n pop.className = 'pop';\n pop.style.left = `${Math.max(12, Math.min(x, win.innerWidth - 372))}px`;\n pop.style.top = `${Math.max(12, Math.min(y + 12, win.innerHeight - 260))}px`;\n const shape = lookup(id);\n const head = doc.createElement('small');\n head.textContent = `${words.key}: `;\n const code = doc.createElement('code');\n code.textContent = id;\n head.append(code);\n const current = doc.createElement('div');\n current.textContent = `${words.original}: ${now}`;\n pop.append(head, current);\n if (!shape || (shape.kind !== 'text' && shape.kind !== 'message')) {\n const note = doc.createElement('div');\n note.textContent = words.notTranslatable;\n pop.append(note, button(words.cancel, closePop));\n root.append(pop);\n return;\n }\n if (shape.params?.length) {\n const params = doc.createElement('small');\n params.textContent = `${words.parameters}: ${shape.params.map(name => `{${name}}${shape.plural?.includes(name) ? ' (plural)' : ''}`).join(', ')}`;\n pop.append(params);\n }\n const input = doc.createElement('textarea');\n input.value = draft().strings[id] ?? now;\n const error = doc.createElement('div');\n error.className = 'err';\n const row = doc.createElement('div');\n row.className = 'row';\n if (draft().strings[id] !== undefined) {\n row.append(button(words.remove, () => { const next = draft(); delete next.strings[id]; save(next); changed = true; closePop(); renderBar(); }));\n }\n row.append(button(words.cancel, closePop), button(words.save, () => {\n const value = input.value.trim();\n const problem = checkEdit(lookup, id, value);\n if (problem) { error.textContent = problem; return; }\n const next = draft();\n next.strings[id] = value;\n save(next);\n changed = true;\n closePop();\n renderBar();\n }, true));\n pop.append(input, error, row);\n root.append(pop);\n input.focus();\n };\n\n /** The marked keys at a click: the element's own text and labels, then up to three parents. */\n const keysAt = (target: Element | null) => {\n for (let el = target, depth = 0; el && depth < 4; el = el.parentElement, depth += 1) {\n const texts = [el.textContent ?? '', el.getAttribute('aria-label') ?? '', el.getAttribute('placeholder') ?? '', el.getAttribute('title') ?? ''];\n const found = texts.flatMap(text => options.marker.ids(text).map(id => ({ id, text: options.marker.strip(text) })));\n if (found.length) return found.filter((item, i) => found.findIndex(other => other.id === item.id) === i).slice(0, 8);\n }\n return [];\n };\n const onClick = (event: MouseEvent) => {\n if (event.composedPath().includes(host)) return;\n const found = keysAt(event.target as Element | null);\n if (!found.length) return;\n event.preventDefault();\n event.stopPropagation();\n if (found.length === 1) { edit(found[0]!.id, event.clientX, event.clientY, found[0]!.text); return; }\n closePop();\n pop = doc.createElement('div');\n pop.className = 'pop';\n pop.style.left = `${Math.max(12, Math.min(event.clientX, win.innerWidth - 372))}px`;\n pop.style.top = `${Math.max(12, Math.min(event.clientY + 12, win.innerHeight - 260))}px`;\n const pick = doc.createElement('div');\n pick.className = 'pick';\n const label = doc.createElement('small');\n label.textContent = words.choose;\n pick.append(label, ...found.map(item => button(`${item.text.slice(0, 60)} · ${item.id}`, () => edit(item.id, event.clientX, event.clientY, item.text))));\n pop.append(pick, button(words.cancel, closePop));\n root.append(pop);\n };\n\n const mount = () => { (doc.body ?? doc.documentElement).append(host); renderBar(); };\n if (doc.body) mount(); else doc.addEventListener('DOMContentLoaded', mount, { once: true });\n win.addEventListener('click', onClick, true);\n return { dispose() { win.removeEventListener('click', onClick, true); host.remove(); } };\n}\n\n/** The draft as the last pack of a delivery (it applies over the others), when it has any string. */\nexport function withDraft(packs: readonly unknown[], storage: Pick<Storage, 'getItem'>, site: string, catalog: string, inventory: PageInventory): unknown[] {\n const draft = readDraft(storage);\n if (!draft || !Object.keys(draft.strings).length) return [...packs];\n const pack = draftPack(draft, site, catalog);\n try { compilePack(pack, inventory); } catch { return [...packs]; }\n return [...packs, pack];\n}\n"]}
1
+ {"version":3,"file":"stringPackTranslate.js","sourceRoot":"","sources":["../../../runtime/src/stringPackTranslate.ts"],"names":[],"mappings":"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","sourcesContent":["// Translate mode for string packs (published as `waybetter/runtime/stringPackTranslate`): the site's\n// strings carry an invisible marker naming their key, so clicking a word on the page tells which\n// key it is. The author types a replacement (checked with the pack rules right there), the edits\n// collect in the tab as a draft pack that applies on the next reload, and Export copies the pack\n// file. Nothing leaves the page; the draft lives in the tab's session storage.\n//\n// The markers change the site's text (invisible characters at the end of each string), so exact\n// comparisons in the site's own code may notice: translate mode is for authors, never on by default,\n// and leaving it reloads the page without markers.\nimport { compilePack, inventoryLookup, validateString, type InventoryLookup, type PageInventory, type StringPack } from './stringPacks.ts';\n\nconst START = '⁣';\nconst DIGITS = ['​', '‌', '‍', '⁠'];\nconst MARKED = /⁣([​‌‍⁠]+)⁣/g;\n\nexport type Marker = {\n /** The value with its key's marker (strings, and functions whose result is a string). */\n mark(id: string, value: unknown): unknown;\n /** The keys marked in a text, in order. */\n ids(text: string): string[];\n /** The text without markers. */\n strip(text: string): string;\n};\n\nexport function createMarker(): Marker {\n const ids: string[] = [];\n const index = new Map<string, number>();\n const code = (id: string) => {\n let n = index.get(id);\n if (n === undefined) { n = ids.length; ids.push(id); index.set(id, n); }\n let digits = '';\n do { digits = DIGITS[n % 4] + digits; n = Math.floor(n / 4); } while (n);\n return START + digits + START;\n };\n return {\n mark(id, value) {\n if (typeof value === 'string') return value + code(id);\n if (typeof value === 'function') {\n const original = value as (...args: unknown[]) => unknown;\n return function (this: unknown, ...args: unknown[]) {\n const out = original.apply(this, args);\n return typeof out === 'string' ? out + code(id) : out;\n };\n }\n return value;\n },\n ids(text) {\n const out: string[] = [];\n for (const match of text.matchAll(MARKED)) {\n let n = 0;\n for (const ch of match[1]!) n = n * 4 + DIGITS.indexOf(ch);\n const id = ids[n];\n if (id && !out.includes(id)) out.push(id);\n }\n return out;\n },\n strip: text => text.replace(MARKED, ''),\n };\n}\n\n/** The author's edits in this tab. */\nexport type TranslateDraft = { language: string; name: string; strings: Record<string, string> };\n\nexport const DRAFT_KEY = 'wayBetter.translateDraft';\n\nexport function readDraft(storage: Pick<Storage, 'getItem'>): TranslateDraft | null {\n try {\n const value = JSON.parse(storage.getItem(DRAFT_KEY) ?? 'null') as TranslateDraft | null;\n return value && typeof value === 'object' && typeof value.strings === 'object' && value.strings !== null ? value : null;\n } catch {\n return null;\n }\n}\n\n/** The draft as a pack document for `site`/`catalog`. */\nexport function draftPack(draft: TranslateDraft, site: string, catalog: string): StringPack {\n return {\n format: 1, site, catalog, language: draft.language || 'en-x-draft', name: draft.name || 'My strings', authors: ['Me'],\n version: '0.1.0', strings: { ...draft.strings },\n };\n}\n\n/** Checks one edit against the key's shape; `null` when it can be used. */\nexport function checkEdit(lookup: InventoryLookup, id: string, value: string): string | null {\n const shape = lookup(id);\n if (!shape) return 'this key is not in the inventory';\n const result = validateString(shape, value);\n return 'reason' in result ? result.reason : null;\n}\n\nexport type TranslateWords = {\n bar: string; hint: string; edits(count: number): string; exportPack: string; copied: string; exit: string; reload: string;\n key: string; original: string; parameters: string; plural: string; save: string; cancel: string; remove: string; notTranslatable: string; choose: string;\n};\n\n/** English defaults (the app passes its own words). */\nexport const TRANSLATE_WORDS: TranslateWords = {\n bar: 'Translate mode', hint: 'Click a word to change it.', edits: count => `${count} ${count === 1 ? 'edit' : 'edits'}`,\n exportPack: 'Copy pack file', copied: 'Pack file copied', exit: 'Leave', reload: 'Reload to see it',\n key: 'Key', original: 'Now', parameters: 'Parameters', plural: 'plural', save: 'Save', cancel: 'Cancel', remove: 'Remove edit',\n notTranslatable: 'This string cannot be changed by a pack yet.', choose: 'Which one?',\n};\n\nexport type TranslateUiOptions = {\n inventory: PageInventory;\n marker: Marker;\n site: string;\n catalog: string;\n /** The bar's and the editor's words; a function is asked again each time they are drawn (the app's language may change). */\n words?: Partial<TranslateWords> | (() => Partial<TranslateWords>);\n /** Leaves translate mode (the app drops the flag and reloads). */\n exit(): void;\n};\n\nconst CSS = `\n:host { all: initial; }\n.bar { position: fixed; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 2147483646; display: flex; gap: 10px; align-items: center;\n padding: 8px 12px; border-radius: 12px; background: #111827; color: #f9fafb; font: 13px/1.3 system-ui, sans-serif; box-shadow: 0 8px 24px rgba(0,0,0,.35); }\n.bar b { font-weight: 600; } .bar span { color: #cbd5e1; }\nbutton { font: inherit; border: 0; border-radius: 8px; padding: 6px 10px; cursor: pointer; background: #334155; color: #f9fafb; }\nbutton.primary { background: #2563eb; }\n.pop { position: fixed; z-index: 2147483647; width: 360px; max-width: calc(100vw - 24px); padding: 12px; border-radius: 12px; background: #fff; color: #111827;\n font: 13px/1.4 system-ui, sans-serif; box-shadow: 0 12px 32px rgba(0,0,0,.3); display: grid; gap: 8px; }\n.pop small { color: #475569; } .pop code { font-size: 12px; color: #334155; }\n.pop textarea { width: 100%; box-sizing: border-box; min-height: 64px; font: inherit; padding: 6px; border: 1px solid #cbd5e1; border-radius: 8px; }\n.pop .row { display: flex; gap: 8px; justify-content: flex-end; } .pop .err { color: #b91c1c; }\n.pop .pick { display: grid; gap: 4px; } .pop .pick button { text-align: left; background: #e2e8f0; color: #111827; }\n`;\n\n/**\n * The page part of translate mode: a bar (edits, copy the pack file, leave) and an editor for the\n * string under a click. Clicks on marked text are the editor's while the mode is on.\n */\nexport function installTranslateUi(win: Window, options: TranslateUiOptions): { dispose(): void } {\n const doc = win.document;\n const currentWords = (): TranslateWords => ({ ...TRANSLATE_WORDS, ...(typeof options.words === 'function' ? options.words() : options.words) });\n const lookup = inventoryLookup(options.inventory);\n const storage = win.sessionStorage;\n const draft = (): TranslateDraft => readDraft(storage) ?? { language: '', name: '', strings: {} };\n const save = (next: TranslateDraft) => { try { storage.setItem(DRAFT_KEY, JSON.stringify(next)); } catch { /* storage full or blocked */ } };\n\n const host = doc.createElement('wb-translate');\n const root = host.attachShadow({ mode: 'open' });\n const style = doc.createElement('style');\n style.textContent = CSS;\n root.append(style);\n const bar = doc.createElement('div');\n bar.className = 'bar';\n root.append(bar);\n let pop: HTMLElement | null = null;\n let changed = false;\n\n const button = (label: string, onClick: () => void, primary = false) => {\n const el = doc.createElement('button');\n el.textContent = label;\n if (primary) el.className = 'primary';\n el.addEventListener('click', event => { event.preventDefault(); event.stopPropagation(); onClick(); });\n return el;\n };\n const renderBar = () => {\n const words = currentWords();\n bar.replaceChildren();\n const title = doc.createElement('b');\n title.textContent = words.bar;\n const hint = doc.createElement('span');\n hint.textContent = `${words.hint} · ${words.edits(Object.keys(draft().strings).length)}`;\n bar.append(title, hint);\n if (changed) bar.append(button(words.reload, () => win.location.reload(), true));\n bar.append(button(words.exportPack, () => {\n const text = JSON.stringify(draftPack(draft(), options.site, options.catalog), null, 2);\n void win.navigator.clipboard?.writeText(text).then(() => { hint.textContent = words.copied; }).catch(() => {});\n }));\n bar.append(button(words.exit, options.exit));\n };\n const closePop = () => { pop?.remove(); pop = null; };\n\n const edit = (id: string, x: number, y: number, now: string) => {\n const words = currentWords();\n closePop();\n pop = doc.createElement('div');\n pop.className = 'pop';\n pop.style.left = `${Math.max(12, Math.min(x, win.innerWidth - 372))}px`;\n pop.style.top = `${Math.max(12, Math.min(y + 12, win.innerHeight - 260))}px`;\n const shape = lookup(id);\n const head = doc.createElement('small');\n head.textContent = `${words.key}: `;\n const code = doc.createElement('code');\n code.textContent = id;\n head.append(code);\n const current = doc.createElement('div');\n current.textContent = `${words.original}: ${now}`;\n pop.append(head, current);\n if (!shape || (shape.kind !== 'text' && shape.kind !== 'message')) {\n const note = doc.createElement('div');\n note.textContent = words.notTranslatable;\n pop.append(note, button(words.cancel, closePop));\n root.append(pop);\n return;\n }\n if (shape.params?.length) {\n const params = doc.createElement('small');\n params.textContent = `${words.parameters}: ${shape.params.map(name => `{${name}}${shape.plural?.includes(name) ? ` (${words.plural})` : ''}`).join(', ')}`;\n pop.append(params);\n }\n const input = doc.createElement('textarea');\n input.value = draft().strings[id] ?? now;\n const error = doc.createElement('div');\n error.className = 'err';\n const row = doc.createElement('div');\n row.className = 'row';\n if (draft().strings[id] !== undefined) {\n row.append(button(words.remove, () => { const next = draft(); delete next.strings[id]; save(next); changed = true; closePop(); renderBar(); }));\n }\n row.append(button(words.cancel, closePop), button(words.save, () => {\n const value = input.value.trim();\n const problem = checkEdit(lookup, id, value);\n if (problem) { error.textContent = problem; return; }\n const next = draft();\n next.strings[id] = value;\n save(next);\n changed = true;\n closePop();\n renderBar();\n }, true));\n pop.append(input, error, row);\n root.append(pop);\n input.focus();\n };\n\n /** The marked keys at a click: the element's own text and labels, then up to three parents. */\n const keysAt = (target: Element | null) => {\n for (let el = target, depth = 0; el && depth < 4; el = el.parentElement, depth += 1) {\n const texts = [el.textContent ?? '', el.getAttribute('aria-label') ?? '', el.getAttribute('placeholder') ?? '', el.getAttribute('title') ?? ''];\n const found = texts.flatMap(text => options.marker.ids(text).map(id => ({ id, text: options.marker.strip(text) })));\n if (found.length) return found.filter((item, i) => found.findIndex(other => other.id === item.id) === i).slice(0, 8);\n }\n return [];\n };\n const onClick = (event: MouseEvent) => {\n const words = currentWords();\n if (event.composedPath().includes(host)) return;\n const found = keysAt(event.target as Element | null);\n if (!found.length) return;\n event.preventDefault();\n event.stopPropagation();\n if (found.length === 1) { edit(found[0]!.id, event.clientX, event.clientY, found[0]!.text); return; }\n closePop();\n pop = doc.createElement('div');\n pop.className = 'pop';\n pop.style.left = `${Math.max(12, Math.min(event.clientX, win.innerWidth - 372))}px`;\n pop.style.top = `${Math.max(12, Math.min(event.clientY + 12, win.innerHeight - 260))}px`;\n const pick = doc.createElement('div');\n pick.className = 'pick';\n const label = doc.createElement('small');\n label.textContent = words.choose;\n pick.append(label, ...found.map(item => button(`${item.text.slice(0, 60)} · ${item.id}`, () => edit(item.id, event.clientX, event.clientY, item.text))));\n pop.append(pick, button(words.cancel, closePop));\n root.append(pop);\n };\n\n const mount = () => { (doc.body ?? doc.documentElement).append(host); renderBar(); };\n if (doc.body) mount(); else doc.addEventListener('DOMContentLoaded', mount, { once: true });\n win.addEventListener('click', onClick, true);\n return { dispose() { win.removeEventListener('click', onClick, true); host.remove(); } };\n}\n\n/** The draft as the last pack of a delivery (it applies over the others), when it has any string. */\nexport function withDraft(packs: readonly unknown[], storage: Pick<Storage, 'getItem'>, site: string, catalog: string, inventory: PageInventory): unknown[] {\n const draft = readDraft(storage);\n if (!draft || !Object.keys(draft.strings).length) return [...packs];\n const pack = draftPack(draft, site, catalog);\n try { compilePack(pack, inventory); } catch { return [...packs]; }\n return [...packs, pack];\n}\n"]}
@@ -4,9 +4,11 @@ type Parts = ReturnType<typeof parseMessage>;
4
4
  * What the site's value for a key is:
5
5
  * - `text`: a plain string;
6
6
  * - `message`: a function of a parameter object that returns a string;
7
- * - `rich`: text with element slots (not replaceable by data yet);
7
+ * - `rich`: text with element slots (a link or a button inside a sentence). It can be replaced when
8
+ * the inventory knows its slot count (`slots`): the pack value then places the site's own
9
+ * elements with `{e0}`, `{e1}`, …, each exactly once (the last may be left out); the value itself is still plain text;
8
10
  * - `format`: a compiled number/date/currency formatter (not text).
9
- * Only `text` and `message` can be replaced by a pack.
11
+ * Only `text`, `message` and `rich` with known `slots` can be replaced by a pack.
10
12
  */
11
13
  export type StringKind = 'text' | 'message' | 'rich' | 'format';
12
14
  export type InventoryEntry = {
@@ -17,6 +19,8 @@ export type InventoryEntry = {
17
19
  plural?: string[];
18
20
  /** Parameters the site's value selects on. */
19
21
  select?: string[];
22
+ /** Element slots of a `rich` value (`{e0}` … `{e<slots-1>}` in a pack value; the last may be left out). Without it the key is closed. */
23
+ slots?: number;
20
24
  /** Tag slot names, for catalogs whose values carry `<tag>…</tag>` slots. */
21
25
  tags?: string[];
22
26
  /** Hash of the base-language value (`hashText`): drift detection; never the text itself. */
@@ -35,6 +39,24 @@ export type StringInventory = {
35
39
  build?: string;
36
40
  entries: Record<string, InventoryEntry>;
37
41
  };
42
+ /**
43
+ * When a pack applies. Without a condition it always does. `languages`: only while the site shows
44
+ * one of these languages (a tag matches itself and its regional forms: `en` matches `en-GB`), for
45
+ * example a rewording of English that must not touch a Turkish page.
46
+ */
47
+ export type PackCondition = {
48
+ languages?: string[];
49
+ };
50
+ /**
51
+ * A pack's rewording rule: whole words replaced inside the site's own text, wherever it appears
52
+ * (`{ "Post": "Tweet", "posts": "tweets" }`). It carries none of the site's sentences, so it keeps
53
+ * working when the site words a string differently, and it applies to a value of the site (or of an
54
+ * earlier pack) that no later pack replaced. Words are matched exactly (case as written), never
55
+ * inside a longer word, and never in a parameter's value (a user named "Post" stays "Post").
56
+ */
57
+ export type PackRewrite = {
58
+ words: Record<string, string>;
59
+ };
38
60
  /** A string pack document: metadata and `key id -> text`. */
39
61
  export type StringPack = {
40
62
  format: 1;
@@ -46,6 +68,10 @@ export type StringPack = {
46
68
  authors: string[];
47
69
  /** A language whose plural rules the pack follows when the runtime does not know `language`. */
48
70
  base?: string;
71
+ /** The pack applies only when this holds (default: always). */
72
+ when?: PackCondition;
73
+ /** Rewords the site's text instead of (or besides) replacing keys. */
74
+ rewrite?: PackRewrite;
49
75
  version?: string;
50
76
  description?: string;
51
77
  strings: Record<string, string>;
@@ -57,12 +83,14 @@ export declare const STRING_PACK_LIMITS: Readonly<{
57
83
  authors: 16;
58
84
  }>;
59
85
  export declare const MAIN_GROUP = "main";
60
- /** Whether a pack can replace a value of this kind. */
61
- export declare function translatable(kind: StringKind): boolean;
86
+ /** Whether a pack can replace a value of this kind (a `rich` one only with known `slots`: pass the shape). */
87
+ export declare function translatable(kind: StringKind | Pick<EntryShape, 'kind' | 'slots'>): boolean;
88
+ /** Whether `language` (the site's) satisfies a pack's `when`. */
89
+ export declare function conditionHolds(when: PackCondition | undefined, language: string | undefined): boolean;
62
90
  /** 53-bit hash of a text as 11 base-36 characters. Same result in every runtime. */
63
91
  export declare function hashText(text: string): string;
64
92
  /** The facts the validator and the engine need about one key. */
65
- export type EntryShape = Pick<InventoryEntry, 'kind' | 'params' | 'plural' | 'select' | 'tags'>;
93
+ export type EntryShape = Pick<InventoryEntry, 'kind' | 'params' | 'plural' | 'select' | 'tags' | 'slots'>;
66
94
  export type InventoryLookup = (id: string) => EntryShape | undefined;
67
95
  /**
68
96
  * The inventory as it ships inside a page runtime: ids and shapes only, no hashes, a fraction of
@@ -75,6 +103,8 @@ export type PageInventory = {
75
103
  text: string;
76
104
  message: Record<string, string>;
77
105
  closed: string;
106
+ /** Rich entries a pack can replace: id -> slot count. */
107
+ rich?: Record<string, number>;
78
108
  /** A slice: only the keys some pack names. A key missing from a slice is not a new key of the site. */
79
109
  partial?: boolean;
80
110
  };
@@ -105,10 +135,20 @@ export declare function validateString(shape: EntryShape, value: unknown): {
105
135
  } | {
106
136
  reason: string;
107
137
  };
138
+ /**
139
+ * A replaced `rich` value in the shape X's own renderer reads: piece `n` is shown, then the site's
140
+ * element `order[n]` (the last piece has no element after it).
141
+ */
142
+ export type RichTemplate = {
143
+ readonly pieces: readonly string[];
144
+ readonly order: readonly number[];
145
+ };
108
146
  export type CompiledValue = {
109
147
  text: string;
110
148
  } | {
111
149
  parts: Parts;
150
+ } | {
151
+ rich: RichTemplate;
112
152
  };
113
153
  export type PackReport = {
114
154
  /** Entries in the pack. */
@@ -127,6 +167,9 @@ export type CompiledPack = {
127
167
  name: string;
128
168
  language: string;
129
169
  base?: string;
170
+ when?: PackCondition;
171
+ /** The pack's word rewriting, ready to apply to a text. */
172
+ rewrite?: (text: string) => string;
130
173
  site: string;
131
174
  catalog: string;
132
175
  values: ReadonlyMap<string, CompiledValue>;
@@ -158,6 +201,11 @@ export type StringEngine = {
158
201
  * Feeding a value this engine produced back in returns the same value (idempotent).
159
202
  */
160
203
  resolve(id: string, original: unknown): unknown;
204
+ /**
205
+ * The language the site shows (what packs with a `when.languages` condition are checked
206
+ * against). Set before the first `resolve`; until then conditional packs do not apply.
207
+ */
208
+ setLanguage(language: string): void;
161
209
  /** The site's own values of the keys a pack changed: what restoring needs. */
162
210
  originals(): ReadonlyMap<string, unknown>;
163
211
  stats(): EngineStats;
@@ -22,10 +22,23 @@ export const MAIN_GROUP = 'main';
22
22
  const TAG = /^[A-Za-z]{2,8}(?:-[A-Za-z0-9]{1,8})*$/;
23
23
  const IDENT = /^[a-z][a-z0-9-]{0,31}$/;
24
24
  const CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f‪-‮⁦-⁩]/;
25
+ const REWRITE_FROM = /^[\p{L}\p{N}]{1,40}$/u;
26
+ const REWRITE_TO = /^[\p{L}\p{N}][\p{L}\p{N}'’-]{0,39}$/u;
27
+ const MAX_REWRITES = 64;
25
28
  const CATEGORIES = new Set(['zero', 'one', 'two', 'few', 'many', 'other']);
26
- /** Whether a pack can replace a value of this kind. */
29
+ /** Whether a pack can replace a value of this kind (a `rich` one only with known `slots`: pass the shape). */
27
30
  export function translatable(kind) {
28
- return kind === 'text' || kind === 'message';
31
+ const { kind: k, slots } = typeof kind === 'string' ? { kind, slots: undefined } : kind;
32
+ return k === 'text' || k === 'message' || (k === 'rich' && slots !== undefined);
33
+ }
34
+ /** Whether `language` (the site's) satisfies a pack's `when`. */
35
+ export function conditionHolds(when, language) {
36
+ if (!when?.languages)
37
+ return true;
38
+ if (!language)
39
+ return false;
40
+ const have = language.toLowerCase();
41
+ return when.languages.some(tag => { const want = tag.toLowerCase(); return have === want || have.startsWith(`${want}-`); });
29
42
  }
30
43
  /** 53-bit hash of a text as 11 base-36 characters. Same result in every runtime. */
31
44
  export function hashText(text) {
@@ -44,8 +57,11 @@ function compact(site, catalog, shapes) {
44
57
  const text = [];
45
58
  const closed = [];
46
59
  const message = {};
60
+ const rich = {};
47
61
  for (const [id, entry] of shapes) {
48
- if (entry.kind === 'text' && !entry.tags?.length)
62
+ if (entry.kind === 'rich' && entry.slots !== undefined && !entry.tags?.length)
63
+ rich[id] = entry.slots;
64
+ else if (entry.kind === 'text' && !entry.tags?.length)
49
65
  text.push(id);
50
66
  else if (entry.kind === 'message' && !entry.tags?.length) {
51
67
  message[id] = (entry.params ?? []).map(name => `${name}${entry.plural?.includes(name) ? '*' : ''}${entry.select?.includes(name) ? '?' : ''}`).join(' ');
@@ -53,7 +69,7 @@ function compact(site, catalog, shapes) {
53
69
  else
54
70
  closed.push(id);
55
71
  }
56
- return { site, catalog, text: text.join(' '), message, closed: closed.join(' ') };
72
+ return { site, catalog, text: text.join(' '), message, closed: closed.join(' '), ...(Object.keys(rich).length ? { rich } : {}) };
57
73
  }
58
74
  export function packInventory(inventory) {
59
75
  return compact(inventory.site, inventory.catalog, Object.entries(inventory.entries));
@@ -81,6 +97,7 @@ export function isPageInventory(input) {
81
97
  return typeof input.site === 'string' && IDENT.test(input.site) && typeof input.catalog === 'string' && IDENT.test(input.catalog)
82
98
  && ids(input.text) && ids(input.closed) && isRecord(input.message)
83
99
  && Object.entries(input.message).every(([id, names]) => /^[\w$.-]+$/.test(id) && typeof names === 'string' && /^[\w$*? ]*$/.test(names))
100
+ && (input.rich === undefined || (isRecord(input.rich) && Object.entries(input.rich).every(([id, n]) => /^[\w$.-]+$/.test(id) && Number.isInteger(n) && n >= 0 && n <= 32)))
84
101
  && (input.partial === undefined || typeof input.partial === 'boolean');
85
102
  }
86
103
  const TEXT_SHAPE = Object.freeze({ kind: 'text' });
@@ -100,6 +117,9 @@ export function inventoryLookup(inventory) {
100
117
  if (inventory.closed)
101
118
  for (const id of inventory.closed.split(' '))
102
119
  map.set(id, CLOSED_SHAPE);
120
+ if (inventory.rich)
121
+ for (const [id, slots] of Object.entries(inventory.rich))
122
+ map.set(id, { kind: 'rich', slots });
103
123
  for (const [id, spec] of Object.entries(inventory.message)) {
104
124
  const names = spec ? spec.split(' ') : [];
105
125
  map.set(id, {
@@ -137,6 +157,22 @@ export function checkPackShape(input) {
137
157
  || input.authors.some(author => typeof author !== 'string' || !author.trim() || author.length > STRING_PACK_LIMITS.name || CONTROL.test(author) || /[<>]/.test(author))) {
138
158
  error(`"authors" must be a list of at most ${STRING_PACK_LIMITS.authors} plain-text names`);
139
159
  }
160
+ if (input.when !== undefined) {
161
+ const when = input.when;
162
+ if (!isRecord(when) || Object.keys(when).some(key => key !== 'languages'))
163
+ error('"when" is an object with "languages"');
164
+ else if (when.languages !== undefined && (!Array.isArray(when.languages) || !when.languages.length || when.languages.length > 16
165
+ || when.languages.some(tag => typeof tag !== 'string' || !TAG.test(tag))))
166
+ error('"when.languages" must be a list of 1 to 16 language tags');
167
+ }
168
+ if (input.rewrite !== undefined) {
169
+ const rewrite = input.rewrite;
170
+ const words = isRecord(rewrite) ? rewrite.words : undefined;
171
+ if (!isRecord(rewrite) || Object.keys(rewrite).some(key => key !== 'words') || !isRecord(words) || !Object.keys(words).length || Object.keys(words).length > MAX_REWRITES
172
+ || Object.entries(words).some(([from, to]) => !REWRITE_FROM.test(from) || typeof to !== 'string' || !REWRITE_TO.test(to))) {
173
+ error(`"rewrite" is { "words": { "Word": "Replacement" } } with 1 to ${MAX_REWRITES} plain words`);
174
+ }
175
+ }
140
176
  for (const field of ['version', 'description']) {
141
177
  const value = input[field];
142
178
  if (value !== undefined && (typeof value !== 'string' || value.length > 400 || CONTROL.test(value) || /[<>]/.test(value)))
@@ -146,7 +182,7 @@ export function checkPackShape(input) {
146
182
  error('"strings" must be an object of key id -> text');
147
183
  else if (Object.keys(input.strings).length > STRING_PACK_LIMITS.strings)
148
184
  error(`"strings" has more than ${STRING_PACK_LIMITS.strings} entries`);
149
- const known = new Set(['$schema', 'format', 'site', 'catalog', 'language', 'base', 'name', 'authors', 'version', 'description', 'strings']);
185
+ const known = new Set(['$schema', 'format', 'site', 'catalog', 'language', 'base', 'when', 'rewrite', 'name', 'authors', 'version', 'description', 'strings']);
150
186
  for (const key of Object.keys(input))
151
187
  if (!known.has(key))
152
188
  issues.push({ level: 'warning', message: `unknown field "${key}" is ignored` });
@@ -224,7 +260,7 @@ function checkParts(parts, shape) {
224
260
  * reason it is refused (the original then stays).
225
261
  */
226
262
  export function validateString(shape, value) {
227
- if (!translatable(shape.kind))
263
+ if (!translatable(shape))
228
264
  return { reason: `not translatable yet (${shape.kind})` };
229
265
  if (typeof value !== 'string')
230
266
  return { reason: 'the value must be text' };
@@ -244,6 +280,23 @@ export function validateString(shape, value) {
244
280
  catch (error) {
245
281
  return { reason: `invalid message syntax: ${error instanceof Error ? error.message : String(error)}` };
246
282
  }
283
+ if (shape.kind === 'rich') {
284
+ // Element slots: `{e0}` … `{e<slots-1>}`, each exactly once (the last may be left out); no plural, select or other parameter.
285
+ const slots = Array.from({ length: shape.slots ?? 0 }, (_, index) => `e${index}`);
286
+ const bad = checkParts(parts, { kind: 'message', params: slots });
287
+ if (bad)
288
+ return { reason: bad };
289
+ const used = parts.flatMap(part => (typeof part !== 'string' && part.kind === 'arg' ? [part.name] : []));
290
+ for (const name of slots) {
291
+ const count = used.filter(item => item === name).length;
292
+ // The element after the last piece is not always there: that one slot may be left out.
293
+ if (count === 0 && name === slots[slots.length - 1] && slots.length > 0)
294
+ continue;
295
+ if (count !== 1)
296
+ return { reason: count ? `{${name}} (the original's element) must appear once, not ${count} times` : `the original's element {${name}} is missing` };
297
+ }
298
+ return { parts };
299
+ }
247
300
  const problem = checkParts(parts, shape.kind === 'message' ? shape : { kind: 'text' });
248
301
  return problem ? { reason: problem } : { parts };
249
302
  }
@@ -251,6 +304,31 @@ export function validateString(shape, value) {
251
304
  function literal(parts) {
252
305
  return parts.map(part => (typeof part === 'string' ? part : '')).join('');
253
306
  }
307
+ /** The word-for-word rewriting of a validated `rewrite` rule. */
308
+ function compileRewrite(rule) {
309
+ const words = new Map(Object.entries(rule.words));
310
+ // Words are letters and digits only (checked), so they need no escaping.
311
+ const alternatives = [...words.keys()].sort((a, b) => b.length - a.length).join('|');
312
+ const pattern = new RegExp(String.raw `(?<![\p{L}\p{N}_])(?:${alternatives})(?![\p{L}\p{N}_])`, 'gu');
313
+ return text => text.replace(pattern, word => words.get(word) ?? word);
314
+ }
315
+ /** A validated rich value as pieces and element order. */
316
+ function richTemplate(parts) {
317
+ const pieces = [];
318
+ const order = [];
319
+ let current = '';
320
+ for (const part of parts) {
321
+ if (typeof part === 'string')
322
+ current += part;
323
+ else if (part.kind === 'arg') {
324
+ pieces.push(current);
325
+ order.push(Number(part.name.slice(1)));
326
+ current = '';
327
+ }
328
+ }
329
+ pieces.push(current);
330
+ return Object.freeze({ pieces: Object.freeze(pieces), order: Object.freeze(order) });
331
+ }
254
332
  /**
255
333
  * Validates a pack against an inventory. Throws on a malformed envelope or a pack for another
256
334
  * site/catalog; every other problem is per key and lands in `report`.
@@ -278,10 +356,10 @@ export function compilePack(input, inventory) {
278
356
  report.rejected.push({ id, reason: result.reason });
279
357
  continue;
280
358
  }
281
- values.set(id, shape.kind === 'text' ? { text: literal(result.parts) } : { parts: result.parts });
359
+ values.set(id, shape.kind === 'text' ? { text: literal(result.parts) } : shape.kind === 'rich' ? { rich: richTemplate(result.parts) } : { parts: result.parts });
282
360
  report.valid += 1;
283
361
  }
284
- return { name: pack.name, language: pack.language, base: pack.base, site: pack.site, catalog: pack.catalog, values, report };
362
+ return { name: pack.name, language: pack.language, base: pack.base, when: pack.when, rewrite: pack.rewrite ? compileRewrite(pack.rewrite) : undefined, site: pack.site, catalog: pack.catalog, values, report };
285
363
  }
286
364
  // -------------------------------------------------------------------------------------------
287
365
  // Interpreting a message (closures over the parsed parts; nothing is evaluated)
@@ -343,6 +421,49 @@ export function renderMessage(parts, params, languages, pound = undefined) {
343
421
  }
344
422
  return out;
345
423
  }
424
+ /** The site's (or a pack's) value of one key with `rewrite` applied; a message keeps its parameters' values as they are. */
425
+ function rewriteValue(shape, value, rewrite) {
426
+ if (typeof value === 'string')
427
+ return rewrite(value);
428
+ if (Array.isArray(value)) {
429
+ const mapped = value.map(piece => (typeof piece === 'string' ? rewrite(piece) : piece));
430
+ return mapped.some((piece, index) => piece !== value[index]) ? mapped : value;
431
+ }
432
+ if (typeof value === 'function') {
433
+ const message = value;
434
+ const plural = new Set(shape.plural ?? []);
435
+ return function (params) {
436
+ const input = isRecord(params) ? params : {};
437
+ // The parameters go through as private markers, so only the site's own words are reworded.
438
+ const forward = { ...input };
439
+ const held = [];
440
+ for (const name of shape.params ?? []) {
441
+ if (plural.has(name) || !(name in input))
442
+ continue;
443
+ const marker = `\uE000${held.length}\uE001`;
444
+ held.push([marker, input[name]]);
445
+ forward[name] = marker;
446
+ }
447
+ try {
448
+ const out = message.call(this, forward);
449
+ if (typeof out !== 'string')
450
+ return message.call(this, params);
451
+ let text = rewrite(out);
452
+ for (const [marker, original] of held)
453
+ text = text.split(marker).join(String(original));
454
+ return text;
455
+ }
456
+ catch {
457
+ return message.call(this, params);
458
+ }
459
+ };
460
+ }
461
+ if (typeof value === 'object' && value !== null && 'pieces' in value) {
462
+ const template = value;
463
+ return Object.freeze({ pieces: Object.freeze(template.pieces.map(rewrite)), order: template.order });
464
+ }
465
+ return value;
466
+ }
346
467
  const MAX_LISTED = 200;
347
468
  /** `packs` in application order: a later pack wins over an earlier one for the same key. */
348
469
  export function createStringEngine(inventory, packs) {
@@ -355,55 +476,70 @@ export function createStringEngine(inventory, packs) {
355
476
  const seen = new Set();
356
477
  const unlisted = new Set();
357
478
  const mismatched = new Set();
479
+ let siteLanguage;
358
480
  const winner = (id) => {
359
481
  for (let index = packs.length - 1; index >= 0; index -= 1) {
482
+ if (!conditionHolds(packs[index].when, siteLanguage))
483
+ continue;
360
484
  const value = packs[index].values.get(id);
361
485
  if (value)
362
- return { pack: packs[index], value };
486
+ return { pack: packs[index], value, index };
363
487
  }
364
488
  return null;
365
489
  };
366
490
  const resolve = (id, incoming) => {
367
491
  let original = incoming;
368
492
  // A value of ours coming back in (a second application): work from the site's own value again.
369
- if (typeof incoming === 'function' && produced.has(incoming))
493
+ if ((typeof incoming === 'function' || (typeof incoming === 'object' && incoming !== null)) && produced.has(incoming))
370
494
  original = produced.get(incoming);
371
495
  else if (typeof incoming === 'string' && applied.has(id) && applied.get(id) === incoming)
372
496
  original = originals.get(id);
373
497
  seen.add(id);
374
498
  const found = winner(id);
375
- if (!found) {
499
+ // Word rewriting of the packs after the one whose text won (all of them when none did).
500
+ const rewrites = packs.flatMap((pack, index) => (index > (found?.index ?? -1) && pack.rewrite && conditionHolds(pack.when, siteLanguage) ? [pack.rewrite] : []));
501
+ if (!found && !rewrites.length) {
376
502
  if (whole && unlisted.size < MAX_LISTED && !lookup(id))
377
503
  unlisted.add(id);
378
504
  return original;
379
505
  }
380
506
  const shape = lookup(id);
381
- if (!shape || !translatable(shape.kind))
507
+ if (!shape || shape.kind === 'format' || (found && !translatable(shape)))
382
508
  return original;
383
- if (shape.kind === 'text' ? typeof original !== 'string' : typeof original !== 'function') {
509
+ if (shape.kind === 'text' ? typeof original !== 'string' : shape.kind === 'rich' ? !Array.isArray(original) : typeof original !== 'function') {
384
510
  if (mismatched.size < MAX_LISTED)
385
511
  mismatched.add(id);
386
512
  return original;
387
513
  }
388
514
  if (originals.has(id) && originals.get(id) === original)
389
515
  return applied.get(id);
390
- let next;
391
- if ('text' in found.value) {
392
- next = found.value.text;
393
- }
394
- else {
395
- const parts = found.value.parts;
396
- const languages = [found.pack.language, found.pack.base];
397
- const message = (params) => renderMessage(parts, isRecord(params) ? params : {}, languages);
398
- produced.set(message, original);
399
- next = message;
516
+ let next = original;
517
+ if (found) {
518
+ if ('text' in found.value) {
519
+ next = found.value.text;
520
+ }
521
+ else if ('rich' in found.value) {
522
+ next = found.value.rich;
523
+ }
524
+ else {
525
+ const parts = found.value.parts;
526
+ const languages = [found.pack.language, found.pack.base];
527
+ next = (params) => renderMessage(parts, isRecord(params) ? params : {}, languages);
528
+ }
400
529
  }
530
+ for (const rewrite of rewrites)
531
+ next = rewriteValue(shape, next, rewrite);
532
+ if (next === original)
533
+ return original;
534
+ if (typeof next === 'function' || typeof next === 'object')
535
+ produced.set(next, original);
401
536
  originals.set(id, original);
402
537
  applied.set(id, next);
403
538
  return next;
404
539
  };
405
540
  return {
406
541
  resolve,
542
+ setLanguage(language) { siteLanguage = language; },
407
543
  originals: () => originals,
408
544
  stats: () => ({ seen: seen.size, applied: applied.size, unlisted: [...unlisted], mismatched: [...mismatched] }),
409
545
  };
@@ -432,7 +568,7 @@ export function coverage(inventory, pack) {
432
568
  for (const [id, entry] of Object.entries(inventory.entries)) {
433
569
  const group = (out[entry.group ?? MAIN_GROUP] ??= { translatable: 0, translated: 0, closed: 0 });
434
570
  for (const bucket of [group, out.all]) {
435
- if (!translatable(entry.kind)) {
571
+ if (!translatable(entry)) {
436
572
  bucket.closed += 1;
437
573
  continue;
438
574
  }
@@ -457,7 +593,7 @@ export function driftReport(inventory, observed, loaded) {
457
593
  report.added.push(id);
458
594
  continue;
459
595
  }
460
- if (before.kind !== now.kind || !sameList(before.params, now.params) || !sameList(before.plural, now.plural) || !sameList(before.select, now.select) || !sameList(before.tags, now.tags)) {
596
+ if (before.kind !== now.kind || before.slots !== now.slots || !sameList(before.params, now.params) || !sameList(before.plural, now.plural) || !sameList(before.select, now.select) || !sameList(before.tags, now.tags)) {
461
597
  report.reshaped.push(id);
462
598
  }
463
599
  else if (before.hash !== now.hash) {