@embedpdf/plugin-i18n 3.0.0-next.13 → 3.0.0-next.15

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.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/translate.ts","../src/capability.ts","../src/effects.ts","../src/reducer.ts","../src/i18n.plugin.ts","../src/negotiate.ts"],"sourcesContent":["import type { I18nState, TranslateOptions, TranslationDictionary } from './types';\n\n/**\n * The pure lookup core — state in, string out. No platform access anywhere\n * (`Intl.PluralRules` is core ECMAScript, not DOM); this is the part that\n * ports to Rust verbatim.\n */\n\n/** Walk a dotted path ('commands.zoom.in') through a dictionary tree. */\nfunction lookup(\n dict: TranslationDictionary | undefined,\n key: string,\n): string | TranslationDictionary | undefined {\n if (!dict) return undefined;\n let node: string | TranslationDictionary | undefined = dict;\n for (const part of key.split('.')) {\n if (node === undefined || typeof node === 'string') return undefined;\n node = node[part];\n }\n return node;\n}\n\n/** CLDR plural category for a count, with a safe fallback when the runtime\n * lacks ICU data for the locale. */\nfunction pluralCategory(locale: string, count: number): string {\n try {\n return new Intl.PluralRules(locale).select(count);\n } catch {\n return count === 1 ? 'one' : 'other';\n }\n}\n\n/** A leaf hit is a string; a branch object needs a plural pick via `count`. */\nfunction resolveNode(\n node: string | TranslationDictionary | undefined,\n locale: string,\n params?: Record<string, string | number>,\n): string | undefined {\n if (typeof node === 'string') return node;\n if (node !== undefined && typeof params?.count === 'number') {\n const branch = node[pluralCategory(locale, params.count)] ?? node['other'];\n if (typeof branch === 'string') return branch;\n }\n return undefined;\n}\n\n/** Replace `{slot}` markers; unknown slots stay verbatim (visible in QA). */\nexport function interpolate(text: string, params?: Record<string, string | number>): string {\n if (!params) return text;\n return text.replace(/\\{(\\w+)\\}/g, (match, slot) =>\n params[slot] !== undefined ? String(params[slot]) : match,\n );\n}\n\nexport interface TranslateResult {\n readonly text: string;\n /** False when the key missed every pack (text is then the fallback or the key). */\n readonly found: boolean;\n}\n\n/**\n * Resolve a key against the state: current locale → fallback locale →\n * `options.fallback` (interpolated too) → the key itself.\n */\nexport function translate(\n state: I18nState,\n key: string,\n options?: TranslateOptions,\n): TranslateResult {\n const { locale, fallbackLocale, locales } = state;\n const text =\n resolveNode(lookup(locales[locale]?.translations, key), locale, options?.params) ??\n resolveNode(\n lookup(locales[fallbackLocale]?.translations, key),\n fallbackLocale,\n options?.params,\n );\n if (text !== undefined) return { text: interpolate(text, options?.params), found: true };\n if (options?.fallback !== undefined)\n return { text: interpolate(options.fallback, options.params), found: false };\n return { text: key, found: false };\n}\n","import type { PluginContext } from '@embedpdf/core';\nimport { translate } from './translate';\nimport type { I18nAction, I18nCapability, I18nConfig, I18nState, LocaleInfo } from './types';\n\n/**\n * The i18n capability — selectors (pure reads over state) + intents (the only\n * writers). Built synchronously in `createKernel()`, so `t()` works before the\n * engine exists — including inside the viewer shell's loading UI.\n */\nexport function createI18nCapability(\n ctx: PluginContext<I18nState, I18nAction>,\n config: I18nConfig,\n): I18nCapability {\n // Dev signal, once per offender — a missing key otherwise fails silently\n // (by design: `t()` always returns a usable string).\n const warned = new Set<string>();\n const warnOnce = (id: string, message: string) => {\n if (warned.has(id)) return;\n warned.add(id);\n console.warn(message);\n };\n\n return {\n t: (key, options) => {\n const result = translate(ctx.getState(), key, options);\n if (!result.found && options?.fallback === undefined) {\n warnOnce(\n `key:${key}`,\n `[i18n] missing translation \"${key}\" (locale: ${ctx.getState().locale})`,\n );\n }\n return result.text;\n },\n locale: () => ctx.getState().locale,\n dir: () => {\n const { locales, locale } = ctx.getState();\n return locales[locale]?.dir ?? 'ltr';\n },\n locales: () => {\n const { locales } = ctx.getState();\n const known: LocaleInfo[] = Object.values(locales).map((locale) => ({\n code: locale.code,\n name: locale.name,\n dir: locale.dir ?? 'ltr',\n loaded: true,\n }));\n for (const code of Object.keys(config.loaders ?? {})) {\n if (!locales[code]) known.push({ code, name: code, dir: 'ltr', loaded: false });\n }\n return known;\n },\n loading: () => ctx.getState().loading,\n setLocale: (code) => {\n const state = ctx.getState();\n if (state.locales[code]) {\n ctx.dispatch({ type: 'I18N/SET_LOCALE', locale: code });\n } else if (config.loaders?.[code]) {\n ctx.dispatch({ type: 'I18N/LOAD_STARTED', locale: code });\n } else {\n warnOnce(\n `locale:${code}`,\n `[i18n] unknown locale \"${code}\" — not registered and no loader configured`,\n );\n }\n },\n registerLocale: (locale) => ctx.dispatch({ type: 'I18N/REGISTER_LOCALE', locale }),\n };\n}\n","import type { EffectContext } from '@embedpdf/core';\nimport type { I18nAction, I18nConfig, I18nState } from './types';\n\n/**\n * The plugin's ONE side-effect: fetching lazy locale packs. State-driven — a\n * load is `state.loading`, set by the capability (a switch to a lazy pack) or\n * by the initial state (a lazy startup locale), so a load requested before\n * effects ran is picked up here and nothing is lost to boot ordering.\n */\nexport function registerI18nEffects(config: I18nConfig) {\n return (ctx: EffectContext<I18nState, I18nAction>): void => {\n const load = (code: string) => {\n const loader = config.loaders?.[code];\n if (!loader) return; // unreachable via the capability; guards bad dispatches\n loader().then(\n (locale) => {\n ctx.dispatch({ type: 'I18N/REGISTER_LOCALE', locale });\n // Complete the switch only if this load is still the wanted one —\n // the user may have switched again while the pack was in flight.\n if (ctx.getState().loading === code) {\n ctx.dispatch({ type: 'I18N/SET_LOCALE', locale: locale.code });\n }\n },\n () => ctx.dispatch({ type: 'I18N/LOAD_FAILED', locale: code }),\n );\n };\n\n const pending = ctx.getState().loading;\n if (pending) load(pending);\n ctx.watch(\n () => ctx.getState().loading,\n (code) => {\n if (code) load(code);\n },\n );\n };\n}\n","import type { I18nAction, I18nConfig, I18nState, Locale } from './types';\n\n/**\n * Pure and serializable — locale packs are data, so they live HERE, not in a\n * side-table. One source of truth: `t()` is a pure function of this state and\n * reactivity rides the kernel's one change stream (no emitters).\n */\n\nexport function initialI18nState(config: I18nConfig): I18nState {\n const fallbackLocale = config.fallbackLocale ?? 'en';\n const locales: Record<string, Locale> = {};\n for (const locale of config.locales ?? []) locales[locale.code] = locale;\n const locale = config.locale ?? fallbackLocale;\n // A startup locale that is a lazy pack: show the fallback chain until the\n // pack arrives — seeding `loading` makes effects fetch it at startup.\n const needsLoad = !locales[locale] && config.loaders?.[locale] !== undefined;\n return { locale, fallbackLocale, locales, loading: needsLoad ? locale : null };\n}\n\nexport function i18nReducer(state: I18nState, action: I18nAction): I18nState {\n switch (action.type) {\n case 'I18N/SET_LOCALE': {\n // Only registered packs can become current — lazy ones arrive via\n // LOAD_STARTED → REGISTER_LOCALE → SET_LOCALE (effects drive the middle).\n if (!state.locales[action.locale]) return state;\n if (state.locale === action.locale && state.loading === null) return state;\n return { ...state, locale: action.locale, loading: null };\n }\n case 'I18N/REGISTER_LOCALE':\n return { ...state, locales: { ...state.locales, [action.locale.code]: action.locale } };\n case 'I18N/LOAD_STARTED':\n return state.loading === action.locale ? state : { ...state, loading: action.locale };\n case 'I18N/LOAD_FAILED':\n return state.loading === action.locale ? { ...state, loading: null } : state;\n default:\n return state;\n }\n}\n","import { definePlugin } from '@embedpdf/core';\nimport { createI18nCapability } from './capability';\nimport { registerI18nEffects } from './effects';\nimport { i18nReducer, initialI18nState } from './reducer';\nimport { I18nToken } from './types';\nimport type { I18nAction, I18nCapability, I18nConfig, I18nState } from './types';\n\n/**\n * The i18n plugin: workspace-scoped (locale is a workspace concern) with NO\n * dependencies — engine-free, DOM-free. Its capability is built synchronously\n * in `createKernel()`, so the shell translates from the first frame, while\n * the engine is still booting.\n */\nexport const i18nPlugin = (config: I18nConfig = {}) =>\n definePlugin<I18nState, I18nAction, I18nCapability>({\n id: 'i18n',\n scope: 'workspace',\n token: I18nToken,\n initialState: () => initialI18nState(config),\n reduce: i18nReducer,\n capability: (ctx) => createI18nCapability(ctx, config),\n effects: registerI18nEffects(config),\n });\n","/**\n * Pure BCP-47 locale negotiation — strings in, string out. The plugin never\n * reads the platform; the EMBEDDER feeds it whatever request list its\n * environment offers:\n *\n * ```ts\n * // browser shell: negotiateLocale(['en', 'es', 'ar'], navigator.languages)\n * // server render: negotiateLocale(codes, parseAcceptLanguage(header))\n * ```\n *\n * Matching, per requested code in preference order:\n * 1. exact match (case-insensitive): 'es-MX' → 'es-MX'\n * 2. request narrowed to its language: 'en-GB' → 'en'\n * 3. any available dialect of the language: 'zh' → 'zh-Hans'\n *\n * Returns the matched code exactly as it appears in `available`, or null.\n */\nexport function negotiateLocale(\n available: readonly string[],\n requested: readonly string[],\n): string | null {\n const byCanon = new Map<string, string>();\n for (const code of available) {\n const canon = code.toLowerCase();\n if (!byCanon.has(canon)) byCanon.set(canon, code);\n }\n const languageOf = (code: string) => code.toLowerCase().split('-')[0];\n\n for (const want of requested) {\n const exact = byCanon.get(want.toLowerCase());\n if (exact !== undefined) return exact;\n }\n for (const want of requested) {\n const narrowed = byCanon.get(languageOf(want));\n if (narrowed !== undefined) return narrowed;\n }\n for (const want of requested) {\n const language = languageOf(want);\n for (const [canon, original] of byCanon) {\n if (languageOf(canon) === language) return original;\n }\n }\n return null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["setLocale","setCurrentLocale"],"sources":["../src/model.ts","../src/sync/loaders.ts","../src/negotiate.ts","../src/strings/de.ts","../src/strings/en.ts","../src/strings/es.ts","../src/strings/fr.ts","../src/strings/ja.ts","../src/strings/nl.ts","../src/strings/sv.ts","../src/strings/zh-cn.ts","../src/strings/index.ts","../src/translate.ts","../src/controller.ts","../src/i18n.plugin.ts","../src/state.ts"],"sourcesContent":["/**\n * The i18n state: the current language, the loaded languages' strings, and the\n * language being loaded. Plain data, so `t()` is a pure function of it. Every\n * function below is a pure transition; the controller applies them with\n * `ctx.state.update`.\n */\nimport type { Locale, TranslationDictionary } from './contract';\n\nexport interface I18nState {\n /** The current language's code. */\n readonly locale: string;\n /** The loaded languages, by code: what `t()` reads. */\n readonly locales: Readonly<Record<string, Locale>>;\n /** The code of a language in `loaders` being fetched, or `null`. */\n readonly loading: string | null;\n /** Strings added to a language before it loaded, by code; they merge in when it does. */\n readonly waitingTranslations: Readonly<Record<string, TranslationDictionary>>;\n}\n\n/** Nothing loaded yet: the controller seeds the state from the settings. */\nexport const initialI18nState = (): I18nState => ({\n locale: '',\n locales: {},\n loading: null,\n waitingTranslations: {},\n});\n\n/**\n * The state the settings start with: their languages loaded, and their `locale` current. A start\n * language that is still to load shows `fallbackLocale` meanwhile, and `loading` names it, so the\n * loader fetches it once connected.\n */\nexport function seedFromSettings(\n state: I18nState,\n settings: {\n readonly locale: string | null;\n readonly fallbackLocale: string;\n readonly locales: readonly Locale[];\n readonly loaders: Readonly<Record<string, unknown>>;\n },\n): I18nState {\n const seeded = settings.locales.reduce(registerLocale, state);\n const locale = settings.locale ?? settings.fallbackLocale;\n if (seeded.locales[locale] || settings.loaders[locale] === undefined) {\n return { ...seeded, locale };\n }\n return { ...seeded, locale: settings.fallbackLocale, loading: locale };\n}\n\n/** Deep merge of two dictionaries: later leaves win, branches merge. */\nexport function mergeTranslations(\n base: TranslationDictionary,\n patch: TranslationDictionary,\n): TranslationDictionary {\n const merged: Record<string, string | TranslationDictionary> = { ...base };\n for (const [key, value] of Object.entries(patch)) {\n const current = merged[key];\n merged[key] =\n typeof value === 'object' && typeof current === 'object'\n ? mergeTranslations(current, value)\n : value;\n }\n return merged;\n}\n\nconst hasDottedKey = (dictionary: TranslationDictionary): boolean =>\n Object.entries(dictionary).some(\n ([key, value]) => key.includes('.') || (typeof value === 'object' && hasDottedKey(value)),\n );\n\n/**\n * Dotted keys as branches, the shape `t()` walks: `{ 'review.reject': 'Afwijzen' }` is\n * `{ review: { reject: 'Afwijzen' } }`. Keys merge in order, so a later one wins. A dictionary\n * without a dotted key comes back as it is.\n */\nexport function nestKeys(dictionary: TranslationDictionary): TranslationDictionary {\n if (!hasDottedKey(dictionary)) return dictionary;\n let nested: TranslationDictionary = {};\n for (const [key, value] of Object.entries(dictionary)) {\n const leaf = typeof value === 'object' ? nestKeys(value) : value;\n const branch = key\n .split('.')\n .reduceRight<string | TranslationDictionary>((inner, part) => ({ [part]: inner }), leaf);\n nested = mergeTranslations(nested, branch as TranslationDictionary);\n }\n return nested;\n}\n\nconst withNestedKeys = (locale: Locale): Locale => {\n const translations = nestKeys(locale.translations);\n return translations === locale.translations ? locale : { ...locale, translations };\n};\n\n/**\n * Switch to a loaded language and end any load. A code that isn't loaded changes nothing: a\n * language in `loaders` becomes current through {@link startLocaleLoad}, {@link registerLocale}\n * and then this transition, which the loader drives.\n */\nexport function setLocale(state: I18nState, code: string): I18nState {\n if (!state.locales[code]) return state;\n if (state.locale === code && state.loading === null) return state;\n return { ...state, locale: code, loading: null };\n}\n\n/** Add a language, with any strings that were added to it before it loaded on top. */\nexport function registerLocale(state: I18nState, locale: Locale): I18nState {\n const pack = withNestedKeys(locale);\n const waiting = state.waitingTranslations[locale.code];\n if (!waiting) return { ...state, locales: { ...state.locales, [locale.code]: pack } };\n const { [locale.code]: _merged, ...stillWaiting } = state.waitingTranslations;\n return {\n ...state,\n locales: {\n ...state.locales,\n [locale.code]: { ...pack, translations: mergeTranslations(pack.translations, waiting) },\n },\n waitingTranslations: stillWaiting,\n };\n}\n\nexport function unregisterLocale(state: I18nState, code: string): I18nState {\n if (!state.locales[code]) return state;\n const { [code]: _removed, ...locales } = state.locales;\n return { ...state, locales };\n}\n\n/**\n * Merge strings into a language, later keys winning. For a language that isn't loaded yet (one in\n * `loaders`), the strings wait and merge in when it loads; the controller only lets codes with a\n * loader get here.\n */\nexport function addTranslations(\n state: I18nState,\n code: string,\n dictionary: TranslationDictionary,\n): I18nState {\n const strings = nestKeys(dictionary);\n const pack = state.locales[code];\n if (!pack) {\n const waiting = state.waitingTranslations[code] ?? {};\n return {\n ...state,\n waitingTranslations: {\n ...state.waitingTranslations,\n [code]: mergeTranslations(waiting, strings),\n },\n };\n }\n return {\n ...state,\n locales: {\n ...state.locales,\n [code]: { ...pack, translations: mergeTranslations(pack.translations, strings) },\n },\n };\n}\n\n/** A language in `loaders` is wanted: the loader fetches whatever `loading` names. */\nexport function startLocaleLoad(state: I18nState, code: string): I18nState {\n return state.loading === code ? state : { ...state, loading: code };\n}\n\n/** A load ended without switching: it failed or was cancelled. Only the load still wanted ends. */\nexport function endLocaleLoad(state: I18nState, code: string): I18nState {\n return state.loading === code ? { ...state, loading: null } : state;\n}\n","/**\n * The plugin's only side effect: fetching languages from `loaders`. Driven by\n * state: a load is `state.loading`, set by `setLocale` (a switch to a language\n * still to load) or by the seeded state (a start language still to load), so a\n * load asked for before `connect` ran is picked up here.\n */\nimport {\n PluginError,\n toPluginError,\n toPluginErrorInfo,\n type PluginContext,\n type Settings,\n} from '@embedpdf/core';\n\nimport type { I18nSettings } from '../contract';\nimport { endLocaleLoad, registerLocale, setLocale, type I18nState } from '../model';\n\nexport interface LoadSettlers {\n /** A load finished: the language shows (`ok`), or it failed (`error`). */\n settle(code: string, outcome: { ok: true } | { ok: false; error: unknown }): void;\n}\n\nexport function createLoaders(\n ctx: PluginContext<I18nState, I18nSettings>,\n settings: Settings<I18nSettings>,\n settlers: LoadSettlers,\n onFailed: (locale: string, error: unknown) => void,\n) {\n const load = (code: string) => {\n const loader = settings.get().loaders[code];\n if (!loader) {\n // The loader went away after the switch asked for it.\n ctx.state.update(endLocaleLoad, code);\n return;\n }\n loader().then(\n (locale) => {\n ctx.state.update(registerLocale, locale);\n // Switch only if this load is still the wanted one: the user may have\n // switched again while the language was on its way.\n if (ctx.state.get().loading === code) {\n ctx.state.update(setLocale, locale.code);\n settlers.settle(code, { ok: true });\n }\n },\n (error: unknown) => {\n ctx.state.update(endLocaleLoad, code);\n onFailed(code, error);\n settlers.settle(code, {\n ok: false,\n error: new PluginError('operation-failed', 'i18n', `locale '${code}' failed to load`, {\n details: toPluginErrorInfo(toPluginError('i18n', error)),\n }),\n });\n },\n );\n };\n\n /** Fetch the language `loading` names now, and each one asked for after. */\n const connect = (): void => {\n const loadRequested = (code: string | null) => {\n if (code) load(code);\n };\n loadRequested(ctx.state.get().loading);\n ctx.watch(() => ctx.state.get().loading, loadRequested);\n };\n return { connect };\n}\n","/**\n * Pure locale negotiation over language tags (RFC 5646) — strings in, string\n * out. The plugin never reads the platform; the embedder feeds it whatever\n * request list its environment offers:\n *\n * ```ts\n * // browser shell: negotiateLocale(['en', 'es', 'ar'], navigator.languages)\n * // server render: negotiateLocale(codes, parseAcceptLanguage(header))\n * ```\n *\n * Matching, per requested code in preference order:\n * 1. exact match (case-insensitive): `es-MX` → `es-MX`\n * 2. request narrowed to its language: `en-GB` → `en`\n * 3. any available dialect of the language: `zh` → `zh-Hans`\n *\n * Steps 2 and 3 match only within the script the language is written in, as\n * `Intl.Locale#maximize` gives it: `zh-TW` and `zh-HK` are Traditional\n * Chinese, so `zh-CN` (Simplified) isn't theirs, and they get no match until\n * a Traditional language is available. Where the platform can't tell a tag's\n * script, the language alone decides.\n *\n * Returns the matched code exactly as it appears in `available`, or null.\n */\n/** The script a tag's language is written in (`zh-TW` → `Hant`, `zh` → `Hans`), or null. */\nfunction scriptOf(code: string): string | null {\n try {\n return new Intl.Locale(code).maximize().script ?? null;\n } catch {\n return null;\n }\n}\n\n/** Whether two tags are written in the same script, or one of them can't say. */\nfunction sameScript(left: string, right: string): boolean {\n const leftScript = scriptOf(left);\n const rightScript = scriptOf(right);\n return leftScript === null || rightScript === null || leftScript === rightScript;\n}\n\nexport function negotiateLocale(\n available: readonly string[],\n requested: readonly string[],\n): string | null {\n const byCanonical = new Map<string, string>();\n for (const code of available) {\n const canonical = code.toLowerCase();\n if (!byCanonical.has(canonical)) byCanonical.set(canonical, code);\n }\n const languageOf = (code: string) => code.toLowerCase().split('-')[0];\n\n for (const wanted of requested) {\n const exact = byCanonical.get(wanted.toLowerCase());\n if (exact !== undefined) return exact;\n }\n for (const wanted of requested) {\n const narrowed = byCanonical.get(languageOf(wanted));\n if (narrowed !== undefined && sameScript(wanted, narrowed)) return narrowed;\n }\n for (const wanted of requested) {\n const language = languageOf(wanted);\n for (const [canonical, original] of byCanonical) {\n if (languageOf(canonical) === language && sameScript(wanted, original)) return original;\n }\n }\n return null;\n}\n","import type { EmbedpdfStrings } from './en';\n\n/** EmbedPDF's own strings in German. */\nexport const de: EmbedpdfStrings = {\n commands: {\n zoom: {\n in: 'Vergrößern',\n out: 'Verkleinern',\n fitWidth: 'Seitenbreite',\n fitPage: 'Ganze Seite',\n },\n page: {\n next: 'Nächste Seite',\n previous: 'Vorherige Seite',\n first: 'Erste Seite',\n last: 'Letzte Seite',\n },\n view: {\n rotateClockwise: 'Im Uhrzeigersinn drehen',\n rotateCounterclockwise: 'Gegen den Uhrzeigersinn drehen',\n },\n selection: { copy: 'Kopieren' },\n annotation: { delete: 'Löschen' },\n document: { download: 'Herunterladen', print: 'Drucken' },\n tool: {\n pointer: 'Auswählen',\n pan: 'Verschieben',\n highlight: 'Hervorheben',\n underline: 'Unterstreichen',\n strikeout: 'Durchstreichen',\n squiggly: 'Wellenlinie',\n 'insert-text': 'Text einfügen',\n 'replace-text': 'Text ersetzen',\n ink: 'Zeichnen',\n 'ink-highlight': 'Textmarker',\n 'free-text': 'Textkommentar',\n 'free-text-callout': 'Legende',\n note: 'Notiz',\n square: 'Rechteck',\n circle: 'Ellipse',\n line: 'Linie',\n polygon: 'Polygon',\n polyline: 'Polylinie',\n link: 'Link',\n stamp: 'Stempel',\n attachment: 'Datei anhängen',\n redact: 'Schwärzen',\n distance: 'Abstand',\n perimeter: 'Umfang',\n area: 'Fläche',\n calibrate: 'Kalibrieren',\n 'form-fill': 'Formular ausfüllen',\n 'form-edit': 'Formular bearbeiten',\n 'form-text': 'Textfeld',\n 'form-checkbox': 'Kontrollkästchen',\n 'form-radio': 'Optionsfeld',\n 'form-combobox': 'Dropdown-Liste',\n 'form-listbox': 'Listenfeld',\n 'form-signature': 'Unterschriftsfeld',\n },\n },\n};\n","/** EmbedPDF's own strings in English: the shape every other language follows. */\nexport const en = {\n commands: {\n zoom: { in: 'Zoom in', out: 'Zoom out', fitWidth: 'Fit width', fitPage: 'Fit page' },\n page: {\n next: 'Next page',\n previous: 'Previous page',\n first: 'First page',\n last: 'Last page',\n },\n view: {\n rotateClockwise: 'Rotate clockwise',\n rotateCounterclockwise: 'Rotate counterclockwise',\n },\n selection: { copy: 'Copy' },\n annotation: { delete: 'Delete' },\n document: { download: 'Download', print: 'Print' },\n tool: {\n pointer: 'Select',\n pan: 'Pan',\n highlight: 'Highlight',\n underline: 'Underline',\n strikeout: 'Strikethrough',\n squiggly: 'Squiggly underline',\n 'insert-text': 'Insert text',\n 'replace-text': 'Replace text',\n ink: 'Draw',\n 'ink-highlight': 'Highlighter',\n 'free-text': 'Text box',\n 'free-text-callout': 'Callout',\n note: 'Note',\n square: 'Rectangle',\n circle: 'Ellipse',\n line: 'Line',\n polygon: 'Polygon',\n polyline: 'Polyline',\n link: 'Link',\n stamp: 'Stamp',\n attachment: 'Attach file',\n redact: 'Redact',\n distance: 'Distance',\n perimeter: 'Perimeter',\n area: 'Area',\n calibrate: 'Calibrate',\n 'form-fill': 'Fill form',\n 'form-edit': 'Edit form',\n 'form-text': 'Text field',\n 'form-checkbox': 'Checkbox',\n 'form-radio': 'Radio button',\n 'form-combobox': 'Dropdown',\n 'form-listbox': 'List box',\n 'form-signature': 'Signature field',\n },\n },\n} as const;\n\n/** The strings every language has, with their text left open. */\ntype Shape<T> = { readonly [Key in keyof T]: T[Key] extends string ? string : Shape<T[Key]> };\n\n/** EmbedPDF's own strings, as each language has them. */\nexport type EmbedpdfStrings = Shape<typeof en>;\n","import type { EmbedpdfStrings } from './en';\n\n/** EmbedPDF's own strings in Spanish. */\nexport const es: EmbedpdfStrings = {\n commands: {\n zoom: {\n in: 'Acercar',\n out: 'Alejar',\n fitWidth: 'Ajustar al ancho',\n fitPage: 'Ajustar a la página',\n },\n page: {\n next: 'Página siguiente',\n previous: 'Página anterior',\n first: 'Primera página',\n last: 'Última página',\n },\n view: { rotateClockwise: 'Girar a la derecha', rotateCounterclockwise: 'Girar a la izquierda' },\n selection: { copy: 'Copiar' },\n annotation: { delete: 'Eliminar' },\n document: { download: 'Descargar', print: 'Imprimir' },\n tool: {\n pointer: 'Seleccionar',\n pan: 'Desplazar',\n highlight: 'Resaltar',\n underline: 'Subrayar',\n strikeout: 'Tachar',\n squiggly: 'Subrayado ondulado',\n 'insert-text': 'Insertar texto',\n 'replace-text': 'Reemplazar texto',\n ink: 'Dibujar',\n 'ink-highlight': 'Marcador',\n 'free-text': 'Cuadro de texto',\n 'free-text-callout': 'Llamada',\n note: 'Nota',\n square: 'Rectángulo',\n circle: 'Elipse',\n line: 'Línea',\n polygon: 'Polígono',\n polyline: 'Polilínea',\n link: 'Enlace',\n stamp: 'Sello',\n attachment: 'Adjuntar archivo',\n redact: 'Censurar',\n distance: 'Distancia',\n perimeter: 'Perímetro',\n area: 'Área',\n calibrate: 'Calibrar',\n 'form-fill': 'Rellenar formulario',\n 'form-edit': 'Editar formulario',\n 'form-text': 'Campo de texto',\n 'form-checkbox': 'Casilla de verificación',\n 'form-radio': 'Botón de opción',\n 'form-combobox': 'Lista desplegable',\n 'form-listbox': 'Cuadro de lista',\n 'form-signature': 'Campo de firma',\n },\n },\n};\n","import type { EmbedpdfStrings } from './en';\n\n/** EmbedPDF's own strings in French. */\nexport const fr: EmbedpdfStrings = {\n commands: {\n zoom: {\n in: 'Zoom avant',\n out: 'Zoom arrière',\n fitWidth: 'Pleine largeur',\n fitPage: 'Page entière',\n },\n page: {\n next: 'Page suivante',\n previous: 'Page précédente',\n first: 'Première page',\n last: 'Dernière page',\n },\n view: { rotateClockwise: 'Rotation horaire', rotateCounterclockwise: 'Rotation antihoraire' },\n selection: { copy: 'Copier' },\n annotation: { delete: 'Supprimer' },\n document: { download: 'Télécharger', print: 'Imprimer' },\n tool: {\n pointer: 'Sélectionner',\n pan: 'Déplacer',\n highlight: 'Surligner',\n underline: 'Souligner',\n strikeout: 'Barrer',\n squiggly: 'Soulignement ondulé',\n 'insert-text': 'Insérer du texte',\n 'replace-text': 'Remplacer le texte',\n ink: 'Dessiner',\n 'ink-highlight': 'Surligneur',\n 'free-text': 'Zone de texte',\n 'free-text-callout': 'Légende',\n note: 'Note',\n square: 'Rectangle',\n circle: 'Ellipse',\n line: 'Ligne',\n polygon: 'Polygone',\n polyline: 'Polyligne',\n link: 'Lien',\n stamp: 'Tampon',\n attachment: 'Joindre un fichier',\n redact: 'Biffer',\n distance: 'Distance',\n perimeter: 'Périmètre',\n area: 'Aire',\n calibrate: 'Étalonner',\n 'form-fill': 'Remplir le formulaire',\n 'form-edit': 'Modifier le formulaire',\n 'form-text': 'Champ de texte',\n 'form-checkbox': 'Case à cocher',\n 'form-radio': 'Bouton radio',\n 'form-combobox': 'Liste déroulante',\n 'form-listbox': 'Zone de liste',\n 'form-signature': 'Champ de signature',\n },\n },\n};\n","import type { EmbedpdfStrings } from './en';\n\n/** EmbedPDF's own strings in Japanese. */\nexport const ja: EmbedpdfStrings = {\n commands: {\n zoom: { in: '拡大', out: '縮小', fitWidth: '幅に合わせる', fitPage: 'ページ全体を表示' },\n page: {\n next: '次のページ',\n previous: '前のページ',\n first: '最初のページ',\n last: '最後のページ',\n },\n view: { rotateClockwise: '右に回転', rotateCounterclockwise: '左に回転' },\n selection: { copy: 'コピー' },\n annotation: { delete: '削除' },\n document: { download: 'ダウンロード', print: '印刷' },\n tool: {\n pointer: '選択',\n pan: 'パン',\n highlight: 'ハイライト',\n underline: '下線',\n strikeout: '取り消し線',\n squiggly: '波線',\n 'insert-text': 'テキストを挿入',\n 'replace-text': 'テキストを置換',\n ink: '描画',\n 'ink-highlight': '蛍光ペン',\n 'free-text': 'テキストボックス',\n 'free-text-callout': '引き出し線',\n note: 'ノート',\n square: '長方形',\n circle: '楕円',\n line: '線',\n polygon: '多角形',\n polyline: '折れ線',\n link: 'リンク',\n stamp: 'スタンプ',\n attachment: 'ファイルを添付',\n redact: '墨消し',\n distance: '距離',\n perimeter: '周囲長',\n area: '面積',\n calibrate: '校正',\n 'form-fill': 'フォームに入力',\n 'form-edit': 'フォームを編集',\n 'form-text': 'テキストフィールド',\n 'form-checkbox': 'チェックボックス',\n 'form-radio': 'ラジオボタン',\n 'form-combobox': 'ドロップダウン',\n 'form-listbox': 'リストボックス',\n 'form-signature': '署名フィールド',\n },\n },\n};\n","import type { EmbedpdfStrings } from './en';\n\n/** EmbedPDF's own strings in Dutch. */\nexport const nl: EmbedpdfStrings = {\n commands: {\n zoom: { in: 'Inzoomen', out: 'Uitzoomen', fitWidth: 'Paginabreedte', fitPage: 'Hele pagina' },\n page: {\n next: 'Volgende pagina',\n previous: 'Vorige pagina',\n first: 'Eerste pagina',\n last: 'Laatste pagina',\n },\n view: { rotateClockwise: 'Rechtsom draaien', rotateCounterclockwise: 'Linksom draaien' },\n selection: { copy: 'Kopiëren' },\n annotation: { delete: 'Verwijderen' },\n document: { download: 'Downloaden', print: 'Afdrukken' },\n tool: {\n pointer: 'Selecteren',\n pan: 'Verschuiven',\n highlight: 'Markeren',\n underline: 'Onderstrepen',\n strikeout: 'Doorhalen',\n squiggly: 'Golflijn',\n 'insert-text': 'Tekst invoegen',\n 'replace-text': 'Tekst vervangen',\n ink: 'Tekenen',\n 'ink-highlight': 'Markeerstift',\n 'free-text': 'Tekstvak',\n 'free-text-callout': 'Toelichting',\n note: 'Notitie',\n square: 'Rechthoek',\n circle: 'Ellips',\n line: 'Lijn',\n polygon: 'Veelhoek',\n polyline: 'Polylijn',\n link: 'Koppeling',\n stamp: 'Stempel',\n attachment: 'Bestand bijvoegen',\n redact: 'Redigeren',\n distance: 'Afstand',\n perimeter: 'Omtrek',\n area: 'Oppervlakte',\n calibrate: 'Kalibreren',\n 'form-fill': 'Formulier invullen',\n 'form-edit': 'Formulier bewerken',\n 'form-text': 'Tekstveld',\n 'form-checkbox': 'Selectievakje',\n 'form-radio': 'Keuzerondje',\n 'form-combobox': 'Vervolgkeuzelijst',\n 'form-listbox': 'Keuzelijst',\n 'form-signature': 'Handtekeningveld',\n },\n },\n};\n","import type { EmbedpdfStrings } from './en';\n\n/** EmbedPDF's own strings in Swedish. */\nexport const sv: EmbedpdfStrings = {\n commands: {\n zoom: {\n in: 'Zooma in',\n out: 'Zooma ut',\n fitWidth: 'Anpassa till bredd',\n fitPage: 'Anpassa till sida',\n },\n page: {\n next: 'Nästa sida',\n previous: 'Föregående sida',\n first: 'Första sidan',\n last: 'Sista sidan',\n },\n view: { rotateClockwise: 'Rotera medurs', rotateCounterclockwise: 'Rotera moturs' },\n selection: { copy: 'Kopiera' },\n annotation: { delete: 'Ta bort' },\n document: { download: 'Ladda ned', print: 'Skriv ut' },\n tool: {\n pointer: 'Välj',\n pan: 'Panorera',\n highlight: 'Markera',\n underline: 'Stryk under',\n strikeout: 'Stryk över',\n squiggly: 'Vågig understrykning',\n 'insert-text': 'Infoga text',\n 'replace-text': 'Ersätt text',\n ink: 'Rita',\n 'ink-highlight': 'Överstrykningspenna',\n 'free-text': 'Textruta',\n 'free-text-callout': 'Pratbubbla',\n note: 'Anteckning',\n square: 'Rektangel',\n circle: 'Ellips',\n line: 'Linje',\n polygon: 'Polygon',\n polyline: 'Polylinje',\n link: 'Länk',\n stamp: 'Stämpel',\n attachment: 'Bifoga fil',\n redact: 'Maskera',\n distance: 'Avstånd',\n perimeter: 'Omkrets',\n area: 'Area',\n calibrate: 'Kalibrera',\n 'form-fill': 'Fyll i formulär',\n 'form-edit': 'Redigera formulär',\n 'form-text': 'Textfält',\n 'form-checkbox': 'Kryssruta',\n 'form-radio': 'Alternativknapp',\n 'form-combobox': 'Rullgardinsmeny',\n 'form-listbox': 'Listruta',\n 'form-signature': 'Signaturfält',\n },\n },\n};\n","import type { EmbedpdfStrings } from './en';\n\n/** EmbedPDF's own strings in Simplified Chinese. */\nexport const zhCN: EmbedpdfStrings = {\n commands: {\n zoom: { in: '放大', out: '缩小', fitWidth: '适合宽度', fitPage: '适合页面' },\n page: { next: '下一页', previous: '上一页', first: '第一页', last: '最后一页' },\n view: { rotateClockwise: '顺时针旋转', rotateCounterclockwise: '逆时针旋转' },\n selection: { copy: '复制' },\n annotation: { delete: '删除' },\n document: { download: '下载', print: '打印' },\n tool: {\n pointer: '选择',\n pan: '平移',\n highlight: '高亮',\n underline: '下划线',\n strikeout: '删除线',\n squiggly: '波浪线',\n 'insert-text': '插入文本',\n 'replace-text': '替换文本',\n ink: '绘图',\n 'ink-highlight': '荧光笔',\n 'free-text': '文本框',\n 'free-text-callout': '标注',\n note: '便笺',\n square: '矩形',\n circle: '椭圆',\n line: '直线',\n polygon: '多边形',\n polyline: '折线',\n link: '链接',\n stamp: '图章',\n attachment: '附加文件',\n redact: '涂黑',\n distance: '距离',\n perimeter: '周长',\n area: '面积',\n calibrate: '校准',\n 'form-fill': '填写表单',\n 'form-edit': '编辑表单',\n 'form-text': '文本字段',\n 'form-checkbox': '复选框',\n 'form-radio': '单选按钮',\n 'form-combobox': '下拉列表',\n 'form-listbox': '列表框',\n 'form-signature': '签名字段',\n },\n },\n};\n","/**\n * EmbedPDF's own strings, such as the standard commands' labels, in the eight\n * languages of the standard stamps. They sit under the app's strings: a key\n * the app gives a string replaces EmbedPDF's (`addTranslations` under the same\n * key), and they show only in a language the app has loaded.\n */\nimport type { TranslationDictionary } from '../contract';\nimport { negotiateLocale } from '../negotiate';\nimport { de } from './de';\nimport { en } from './en';\nimport { es } from './es';\nimport { fr } from './fr';\nimport { ja } from './ja';\nimport { nl } from './nl';\nimport { sv } from './sv';\nimport { zhCN } from './zh-cn';\n\nconst EMBEDPDF_STRINGS: Readonly<Record<string, TranslationDictionary>> = {\n en,\n nl,\n de,\n fr,\n es,\n 'zh-CN': zhCN,\n sv,\n ja,\n};\n\nconst CODES = Object.keys(EMBEDPDF_STRINGS);\n\n/** Which of EmbedPDF's languages each code reads, decided once per code. */\nconst languageOf = new Map<string, string | null>();\n\n/**\n * EmbedPDF's strings for a language code, matched as `negotiateLocale` matches: `nl-BE` reads\n * Dutch and `zh` Simplified Chinese. `undefined` for a language EmbedPDF has no strings in.\n */\nexport function embedpdfStringsFor(code: string): TranslationDictionary | undefined {\n let language = languageOf.get(code);\n if (language === undefined) {\n language = negotiateLocale(CODES, [code]);\n languageOf.set(code, language);\n }\n return language === null ? undefined : EMBEDPDF_STRINGS[language];\n}\n","/**\n * The lookup: a key in, a string out. Pure, with no platform access\n * (`Intl.PluralRules` is core ECMAScript, not DOM).\n */\nimport type { Locale, TranslateOptions, TranslationDictionary } from './contract';\nimport { embedpdfStringsFor } from './strings';\n\n/** Walk a dotted path (`'commands.zoom.in'`) through a dictionary. */\nfunction lookup(\n dictionary: TranslationDictionary | undefined,\n key: string,\n): string | TranslationDictionary | undefined {\n if (!dictionary) return undefined;\n let node: string | TranslationDictionary | undefined = dictionary;\n for (const part of key.split('.')) {\n if (node === undefined || typeof node === 'string') return undefined;\n node = node[part];\n }\n return node;\n}\n\n/** The language's plural form for a count; `'one'` or `'other'` when the runtime has no rules for it. */\nfunction pluralCategory(locale: string, count: number): string {\n try {\n return new Intl.PluralRules(locale).select(count);\n } catch {\n return count === 1 ? 'one' : 'other';\n }\n}\n\n/** A string is the answer; a branch needs a plural form, picked by `params.count`. */\nfunction resolveNode(\n node: string | TranslationDictionary | undefined,\n locale: string,\n params?: Record<string, string | number>,\n): string | undefined {\n if (typeof node === 'string') return node;\n if (node !== undefined && typeof params?.count === 'number') {\n const branch = node[pluralCategory(locale, params.count)] ?? node['other'];\n if (typeof branch === 'string') return branch;\n }\n return undefined;\n}\n\n/** Fill in `{name}` slots; a slot without a value stays as it is, so it shows while testing. */\nexport function interpolate(text: string, params?: Record<string, string | number>): string {\n if (!params) return text;\n return text.replace(/\\{(\\w+)\\}/g, (match, slot) =>\n params[slot] !== undefined ? String(params[slot]) : match,\n );\n}\n\n/** What a lookup reads: the two languages it tries, and the app's strings. */\nexport interface TranslationSources {\n readonly locale: string;\n readonly fallbackLocale: string;\n /** The app's loaded languages, by code. */\n readonly locales: Readonly<Record<string, Locale>>;\n}\n\nexport interface TranslateResult {\n readonly text: string;\n /** False when no language has the key: `text` is then the fallback or the key. */\n readonly found: boolean;\n}\n\n/** A key in one language: the app's string, then EmbedPDF's own. */\nconst stringIn = (\n sources: TranslationSources,\n code: string,\n key: string,\n params?: Record<string, string | number>,\n): string | undefined =>\n resolveNode(lookup(sources.locales[code]?.translations, key), code, params) ??\n resolveNode(lookup(embedpdfStringsFor(code), key), code, params);\n\n/**\n * A key's string: in the current language (the key, then `options.fallbackKey`), then the same in\n * the fallback language, then `options.fallback` (with its slots filled too), then the key itself.\n */\nexport function translate(\n sources: TranslationSources,\n key: string,\n options?: TranslateOptions,\n): TranslateResult {\n const params = options?.params;\n const fallbackKey = options?.fallbackKey;\n const inLanguage = (code: string) =>\n stringIn(sources, code, key, params) ??\n (fallbackKey === undefined ? undefined : stringIn(sources, code, fallbackKey, params));\n const text = inLanguage(sources.locale) ?? inLanguage(sources.fallbackLocale);\n if (text !== undefined) return { text: interpolate(text, options?.params), found: true };\n if (options?.fallback !== undefined)\n return { text: interpolate(options.fallback, options.params), found: false };\n return { text: key, found: false };\n}\n","/**\n * The i18n controller: reads over the state, the language verbs, the\n * `onLocaleChanged` derivation, and the loader wired at `connect`. Built\n * synchronously in `createKernel()`, so `t()` works before the engine exists.\n */\nimport {\n isPluginError,\n memo,\n PluginError,\n toPluginError,\n toPluginErrorInfo,\n type OperationOptions,\n type PluginContext,\n type Unsubscribe,\n} from '@embedpdf/core';\n\nimport type {\n I18nCapability,\n I18nSettings,\n LocaleChangedEvent,\n LocaleInfo,\n LocaleLoadFailedEvent,\n TranslateOptions,\n} from './contract';\nimport {\n addTranslations,\n endLocaleLoad,\n registerLocale,\n seedFromSettings,\n setLocale as setCurrentLocale,\n startLocaleLoad,\n unregisterLocale,\n type I18nState,\n} from './model';\nimport { createLoaders } from './sync/loaders';\nimport { translate, type TranslationSources } from './translate';\n\nconst unknownLocale = (code: string) =>\n new PluginError('not-found', 'i18n', `unknown locale '${code}': not loaded and not in loaders`);\n\nexport function createI18nController(ctx: PluginContext<I18nState, I18nSettings>) {\n const settings = ctx.settings();\n const localeChanged = ctx.events.source<LocaleChangedEvent>();\n const localeLoadFailed = ctx.events.source<LocaleLoadFailedEvent>();\n\n const state = () => ctx.state.get();\n ctx.state.update(seedFromSettings, settings.get());\n\n // A language change is announced once it shows: never while a language is\n // still loading, and always against the language announced last.\n let announced = state().locale;\n ctx.state.onChange(({ next }) => {\n if (next.loading !== null || next.locale === announced) return;\n const previousLocale = announced;\n announced = next.locale;\n localeChanged.emit({ locale: next.locale, previousLocale });\n });\n\n const sources = (): TranslationSources => ({\n locale: state().locale,\n fallbackLocale: settings.get().fallbackLocale,\n locales: state().locales,\n });\n\n // A missing key would otherwise go unnoticed, since `t()` always returns\n // something to show: warn once per key.\n const warned = new Set<string>();\n const translateKey = (key: string, options?: TranslateOptions): string => {\n const result = translate(sources(), key, options);\n const missing =\n options?.fallbackKey === undefined ? `\"${key}\"` : `\"${key}\" or \"${options.fallbackKey}\"`;\n if (!result.found && options?.fallback === undefined && !warned.has(missing)) {\n warned.add(missing);\n console.warn(`[i18n] missing translation ${missing} (locale: ${state().locale})`);\n }\n return result.text;\n };\n\n /** A new translate function only when the strings it can return may differ. */\n const getTranslator = memo(\n () => [state().locale, state().locales, settings.get().fallbackLocale],\n (_locale, _locales, _fallbackLocale) => (key: string, options?: TranslateOptions) =>\n translateKey(key, options),\n );\n\n /** The languages a picker shows, rebuilt only when the languages or the loaders change. */\n const listLocales = memo(\n () => [state().locales, settings.get().loaders],\n (locales, loaders): readonly LocaleInfo[] => {\n const known: LocaleInfo[] = Object.values(locales).map((locale) => ({\n code: locale.code,\n name: locale.name,\n direction: locale.direction ?? 'ltr',\n loaded: true,\n }));\n for (const code of Object.keys(loaders)) {\n if (!locales[code]) known.push({ code, name: code, direction: 'ltr', loaded: false });\n }\n return known;\n },\n );\n\n /** The caller waiting for a language to load; a newer switch replaces it. */\n const pending = new Map<string, { resolve(): void; reject(error: unknown): void }>();\n const settle = (code: string, outcome: { ok: true } | { ok: false; error: unknown }) => {\n const waiter = pending.get(code);\n if (!waiter) return;\n pending.delete(code);\n if (outcome.ok) waiter.resolve();\n else waiter.reject(outcome.error);\n };\n const supersede = () => {\n for (const [code, waiter] of pending) {\n pending.delete(code);\n waiter.reject(\n new PluginError('operation-cancelled', 'i18n', `switching to '${code}' was replaced`),\n );\n }\n };\n const loaders = createLoaders(ctx, settings, { settle }, (locale, error) =>\n localeLoadFailed.emit({ locale, error: toPluginErrorInfo(toPluginError('i18n', error)) }),\n );\n\n const setLocale = (code: string, options: OperationOptions = {}): Promise<void> => {\n const { signal } = options;\n if (signal?.aborted) {\n return Promise.reject(new PluginError('operation-cancelled', 'i18n', 'operation cancelled'));\n }\n if (state().locales[code]) {\n supersede();\n ctx.state.update(setCurrentLocale, code);\n return Promise.resolve();\n }\n if (!settings.get().loaders[code]) return Promise.reject(unknownLocale(code));\n supersede();\n const loaded = new Promise<void>((resolve, reject) => {\n pending.set(code, { resolve, reject });\n ctx.state.update(startLocaleLoad, code);\n });\n // A cancelled switch stops waiting: the language still registers when it\n // arrives, but doesn't become current.\n signal?.addEventListener(\n 'abort',\n () => {\n if (!pending.delete(code)) return;\n ctx.state.update(endLocaleLoad, code);\n },\n { once: true },\n );\n return ctx.cancellable(signal, loaded);\n };\n\n /** Follow a settings change: new languages register, and a new `locale` becomes current. */\n const followSettings = (): void => {\n let previous = settings.get();\n ctx.listen(settings.api.onSettingsChanged, ({ settings: next, changed }) => {\n if (changed.includes('locales')) {\n for (const locale of previous.locales) {\n if (!next.locales.some((other) => other.code === locale.code)) {\n ctx.state.update(unregisterLocale, locale.code);\n }\n }\n for (const locale of next.locales) {\n if (!previous.locales.includes(locale)) ctx.state.update(registerLocale, locale);\n }\n }\n if (changed.includes('locale')) {\n const code = next.locale ?? next.fallbackLocale;\n // A failed load fires onLocaleLoadFailed; only a code nothing knows needs saying here.\n setLocale(code).catch((error: unknown) => {\n if (isPluginError(error, 'not-found')) console.warn(`[i18n] ${error.message}`);\n });\n }\n previous = next;\n });\n };\n\n const api: I18nCapability = {\n ...settings.api,\n t: translateKey,\n getTranslator,\n hasKey: (key) => translate(sources(), key).found,\n getLocale: () => state().locale,\n getDirection: () => state().locales[state().locale]?.direction ?? 'ltr',\n listLocales,\n getLoadingLocale: () => state().loading,\n setLocale,\n registerLocale: (locale): Unsubscribe => {\n ctx.state.update(registerLocale, locale);\n return () => ctx.state.update(unregisterLocale, locale.code);\n },\n addTranslations: (code, translations) => {\n // A language in `loaders` takes strings before it has loaded; they merge in when it does.\n if (!state().locales[code] && !settings.get().loaders[code]) throw unknownLocale(code);\n ctx.state.update(addTranslations, code, translations);\n },\n onLocaleChanged: localeChanged.on,\n onLocaleLoadFailed: localeLoadFailed.on,\n };\n\n return {\n api,\n connect() {\n loaders.connect();\n followSettings();\n },\n };\n}\n","import { definePlugin } from '@embedpdf/core';\n\nimport { I18N_DEFAULTS, I18nToken, type I18nConfig } from './contract';\nimport { createI18nController } from './controller';\nimport { initialI18nState } from './model';\n\n/**\n * Translations and the current language. Workspace-scoped, with no dependencies and no engine\n * calls: the capability exists from `createKernel()`, so the viewer translates while the engine\n * is still loading. `config` is the settings the app registers, over {@link I18N_DEFAULTS}.\n */\nexport const i18nPlugin = (config?: I18nConfig) =>\n definePlugin({\n id: 'i18n',\n scope: 'workspace',\n token: I18nToken,\n state: initialI18nState,\n settings: { defaults: I18N_DEFAULTS, registered: config },\n create: createI18nController,\n });\n","/**\n * The translations page's State table as code: what `useI18nState()` returns,\n * and the same fields in every other framework.\n */\nimport { defineState } from '@embedpdf/core';\n\nimport { I18nToken } from './contract';\n\nexport const i18nState = defineState(I18nToken, {\n read: (i18n) => ({\n locale: i18n.getLocale(),\n direction: i18n.getDirection(),\n locales: i18n.listLocales(),\n loading: i18n.getLoadingLocale(),\n }),\n empty: { locale: 'en', direction: 'ltr', locales: [], loading: null },\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@embedpdf/plugin-i18n",
3
- "version": "3.0.0-next.13",
3
+ "version": "3.0.0-next.15",
4
4
  "private": false,
5
- "description": "Workspace-scoped translation: locale packs as store state (pure lookup, reactive for free), lazy pack loading via effects, pure BCP-47 negotiation. Engine-free and DOM-free — alive before the engine boots.",
5
+ "description": "Workspace-scoped translation: locale packs as store state (pure lookup, reactive for free), lazy pack loading, pure BCP-47 negotiation. Engine-free and DOM-free — alive before the engine boots.",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
8
8
  "module": "./dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
- "@embedpdf/core": "3.0.0-next.13"
25
+ "@embedpdf/core": "3.0.0-next.15"
26
26
  },
27
27
  "devDependencies": {
28
28
  "typescript": "^5.0.0",
@@ -1,11 +0,0 @@
1
- //#region src/types.ts
2
- const I18nToken = (0, require("@embedpdf/core").createCapabilityToken)("i18n");
3
- //#endregion
4
- Object.defineProperty(exports, "I18nToken", {
5
- enumerable: true,
6
- get: function() {
7
- return I18nToken;
8
- }
9
- });
10
-
11
- //# sourceMappingURL=contract-BIk4Kv-7.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"contract-BIk4Kv-7.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken } from '@embedpdf/core';\n\n/**\n * @embedpdf/plugin-i18n — the contract.\n *\n * Translation is pure data over pure state: locale packs live IN the store\n * (not in a side-table), so `t()` is a pure read, every consumer is reactive\n * through the one kernel change stream, and the whole thing serializes (SSR,\n * snapshots, persist). Workspace-scoped, `requires: []`, never touches the\n * engine or the DOM — the capability is alive from `createKernel()`, before\n * a single WASM byte is fetched. The plugin ships NO strings: mechanism here,\n * content (locale packs) in the product that embeds it.\n */\n\n/** Nested tree of translation strings. Leaves interpolate `{param}` slots. */\nexport interface TranslationDictionary {\n readonly [key: string]: string | TranslationDictionary;\n}\n\nexport interface Locale {\n /** BCP-47 code: 'en', 'es', 'ar', 'zh-Hans'. */\n readonly code: string;\n /** Native display name: 'Español' — what a locale switcher shows. */\n readonly name: string;\n /** Text direction for viewer chrome. Defaults to 'ltr'. */\n readonly dir?: 'ltr' | 'rtl';\n readonly translations: TranslationDictionary;\n}\n\nexport interface I18nState {\n readonly locale: string;\n readonly fallbackLocale: string;\n /** Registered packs by code — the lookup table `t()` reads. */\n readonly locales: Readonly<Record<string, Locale>>;\n /** Code of a lazy pack currently being fetched (drives switcher spinners). */\n readonly loading: string | null;\n}\n\nexport type I18nAction =\n /** Switch to a REGISTERED locale. The reducer ignores unregistered codes —\n * loading a lazy pack goes through LOAD_STARTED (the capability routes). */\n | { type: 'I18N/SET_LOCALE'; locale: string }\n | { type: 'I18N/REGISTER_LOCALE'; locale: Locale }\n | { type: 'I18N/LOAD_STARTED'; locale: string }\n | { type: 'I18N/LOAD_FAILED'; locale: string };\n\nexport interface I18nConfig {\n /**\n * Startup locale. Compute platform inputs OUTSIDE the plugin — it never\n * touches the DOM:\n *\n * ```ts\n * i18nPlugin({ locale: negotiateLocale(codes, navigator.languages) ?? 'en' })\n * ```\n *\n * May name a `loaders` pack: the fallback locale shows until the pack\n * arrives (effects fetch it at startup). Defaults to `fallbackLocale`.\n */\n locale?: string;\n /** The pack tried when a key misses the current locale. Default 'en'. */\n fallbackLocale?: string;\n /** Eagerly available packs. */\n locales?: Locale[];\n /**\n * Lazy packs: code → loader. `setLocale(code)` fetches on demand (in\n * effects), registers the pack, then switches. Until a lazy pack loads, a\n * locale switcher shows its code as the name — register eagerly (packs are\n * small) when you want native names in the switcher up front.\n */\n loaders?: Record<string, () => Promise<Locale>>;\n}\n\nexport interface TranslateOptions {\n /**\n * `{slot}` interpolation values. When `count` is a number and the key\n * resolves to a branch object, the branch is picked by CLDR plural\n * category (`Intl.PluralRules`), falling back to `other`:\n *\n * ```ts\n * // pages: { one: '{count} page', other: '{count} pages' }\n * t('pages', { params: { count: 1 } }) // '1 page'\n * ```\n */\n params?: Record<string, string | number>;\n /** Returned (interpolated) when the key misses every pack — instead of the key. */\n fallback?: string;\n}\n\n/** A locale as a switcher sees it — registered packs plus not-yet-loaded lazy ones. */\nexport interface LocaleInfo {\n readonly code: string;\n /** Native name; the bare code until a lazy pack has loaded. */\n readonly name: string;\n readonly dir: 'ltr' | 'rtl';\n readonly loaded: boolean;\n}\n\nexport interface I18nCapability {\n /** Translate a key: current locale → fallback locale → `options.fallback` → the key. */\n t(key: string, options?: TranslateOptions): string;\n locale(): string;\n /** Text direction of the CURRENT locale — wire to `dir=` on the shell. */\n dir(): 'ltr' | 'rtl';\n /** Every known locale (registered + lazy), in registration order. */\n locales(): LocaleInfo[];\n /** Code of the lazy pack being fetched right now, if any. */\n loading(): string | null;\n /** Switch locale; fetches the pack first when it's a lazy one. */\n setLocale(code: string): void;\n /** Register a pack at runtime (customer-supplied translations). */\n registerLocale(locale: Locale): void;\n}\n\nexport const I18nToken = createCapabilityToken<I18nCapability>('i18n');\n"],"mappings":";AAiHA,MAAa,aAAA,2BAAA,CAAA,CAAA,sBAAA,CAAkD,MAAM"}
@@ -1,7 +0,0 @@
1
- import { createCapabilityToken } from "@embedpdf/core";
2
- //#region src/types.ts
3
- const I18nToken = createCapabilityToken("i18n");
4
- //#endregion
5
- export { I18nToken as t };
6
-
7
- //# sourceMappingURL=contract-D08iYq9-.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"contract-D08iYq9-.js","names":[],"sources":["../src/types.ts"],"sourcesContent":["import { createCapabilityToken } from '@embedpdf/core';\n\n/**\n * @embedpdf/plugin-i18n — the contract.\n *\n * Translation is pure data over pure state: locale packs live IN the store\n * (not in a side-table), so `t()` is a pure read, every consumer is reactive\n * through the one kernel change stream, and the whole thing serializes (SSR,\n * snapshots, persist). Workspace-scoped, `requires: []`, never touches the\n * engine or the DOM — the capability is alive from `createKernel()`, before\n * a single WASM byte is fetched. The plugin ships NO strings: mechanism here,\n * content (locale packs) in the product that embeds it.\n */\n\n/** Nested tree of translation strings. Leaves interpolate `{param}` slots. */\nexport interface TranslationDictionary {\n readonly [key: string]: string | TranslationDictionary;\n}\n\nexport interface Locale {\n /** BCP-47 code: 'en', 'es', 'ar', 'zh-Hans'. */\n readonly code: string;\n /** Native display name: 'Español' — what a locale switcher shows. */\n readonly name: string;\n /** Text direction for viewer chrome. Defaults to 'ltr'. */\n readonly dir?: 'ltr' | 'rtl';\n readonly translations: TranslationDictionary;\n}\n\nexport interface I18nState {\n readonly locale: string;\n readonly fallbackLocale: string;\n /** Registered packs by code — the lookup table `t()` reads. */\n readonly locales: Readonly<Record<string, Locale>>;\n /** Code of a lazy pack currently being fetched (drives switcher spinners). */\n readonly loading: string | null;\n}\n\nexport type I18nAction =\n /** Switch to a REGISTERED locale. The reducer ignores unregistered codes —\n * loading a lazy pack goes through LOAD_STARTED (the capability routes). */\n | { type: 'I18N/SET_LOCALE'; locale: string }\n | { type: 'I18N/REGISTER_LOCALE'; locale: Locale }\n | { type: 'I18N/LOAD_STARTED'; locale: string }\n | { type: 'I18N/LOAD_FAILED'; locale: string };\n\nexport interface I18nConfig {\n /**\n * Startup locale. Compute platform inputs OUTSIDE the plugin — it never\n * touches the DOM:\n *\n * ```ts\n * i18nPlugin({ locale: negotiateLocale(codes, navigator.languages) ?? 'en' })\n * ```\n *\n * May name a `loaders` pack: the fallback locale shows until the pack\n * arrives (effects fetch it at startup). Defaults to `fallbackLocale`.\n */\n locale?: string;\n /** The pack tried when a key misses the current locale. Default 'en'. */\n fallbackLocale?: string;\n /** Eagerly available packs. */\n locales?: Locale[];\n /**\n * Lazy packs: code → loader. `setLocale(code)` fetches on demand (in\n * effects), registers the pack, then switches. Until a lazy pack loads, a\n * locale switcher shows its code as the name — register eagerly (packs are\n * small) when you want native names in the switcher up front.\n */\n loaders?: Record<string, () => Promise<Locale>>;\n}\n\nexport interface TranslateOptions {\n /**\n * `{slot}` interpolation values. When `count` is a number and the key\n * resolves to a branch object, the branch is picked by CLDR plural\n * category (`Intl.PluralRules`), falling back to `other`:\n *\n * ```ts\n * // pages: { one: '{count} page', other: '{count} pages' }\n * t('pages', { params: { count: 1 } }) // '1 page'\n * ```\n */\n params?: Record<string, string | number>;\n /** Returned (interpolated) when the key misses every pack — instead of the key. */\n fallback?: string;\n}\n\n/** A locale as a switcher sees it — registered packs plus not-yet-loaded lazy ones. */\nexport interface LocaleInfo {\n readonly code: string;\n /** Native name; the bare code until a lazy pack has loaded. */\n readonly name: string;\n readonly dir: 'ltr' | 'rtl';\n readonly loaded: boolean;\n}\n\nexport interface I18nCapability {\n /** Translate a key: current locale → fallback locale → `options.fallback` → the key. */\n t(key: string, options?: TranslateOptions): string;\n locale(): string;\n /** Text direction of the CURRENT locale — wire to `dir=` on the shell. */\n dir(): 'ltr' | 'rtl';\n /** Every known locale (registered + lazy), in registration order. */\n locales(): LocaleInfo[];\n /** Code of the lazy pack being fetched right now, if any. */\n loading(): string | null;\n /** Switch locale; fetches the pack first when it's a lazy one. */\n setLocale(code: string): void;\n /** Register a pack at runtime (customer-supplied translations). */\n registerLocale(locale: Locale): void;\n}\n\nexport const I18nToken = createCapabilityToken<I18nCapability>('i18n');\n"],"mappings":";;AAiHA,MAAa,YAAY,sBAAsC,MAAM"}
@@ -1,116 +0,0 @@
1
- //#region src/types.d.ts
2
- /**
3
- * @embedpdf/plugin-i18n — the contract.
4
- *
5
- * Translation is pure data over pure state: locale packs live IN the store
6
- * (not in a side-table), so `t()` is a pure read, every consumer is reactive
7
- * through the one kernel change stream, and the whole thing serializes (SSR,
8
- * snapshots, persist). Workspace-scoped, `requires: []`, never touches the
9
- * engine or the DOM — the capability is alive from `createKernel()`, before
10
- * a single WASM byte is fetched. The plugin ships NO strings: mechanism here,
11
- * content (locale packs) in the product that embeds it.
12
- */
13
- /** Nested tree of translation strings. Leaves interpolate `{param}` slots. */
14
- interface TranslationDictionary {
15
- readonly [key: string]: string | TranslationDictionary;
16
- }
17
- interface Locale {
18
- /** BCP-47 code: 'en', 'es', 'ar', 'zh-Hans'. */
19
- readonly code: string;
20
- /** Native display name: 'Español' — what a locale switcher shows. */
21
- readonly name: string;
22
- /** Text direction for viewer chrome. Defaults to 'ltr'. */
23
- readonly dir?: 'ltr' | 'rtl';
24
- readonly translations: TranslationDictionary;
25
- }
26
- interface I18nState {
27
- readonly locale: string;
28
- readonly fallbackLocale: string;
29
- /** Registered packs by code — the lookup table `t()` reads. */
30
- readonly locales: Readonly<Record<string, Locale>>;
31
- /** Code of a lazy pack currently being fetched (drives switcher spinners). */
32
- readonly loading: string | null;
33
- }
34
- type I18nAction =
35
- /** Switch to a REGISTERED locale. The reducer ignores unregistered codes —
36
- * loading a lazy pack goes through LOAD_STARTED (the capability routes). */
37
- {
38
- type: 'I18N/SET_LOCALE';
39
- locale: string;
40
- } | {
41
- type: 'I18N/REGISTER_LOCALE';
42
- locale: Locale;
43
- } | {
44
- type: 'I18N/LOAD_STARTED';
45
- locale: string;
46
- } | {
47
- type: 'I18N/LOAD_FAILED';
48
- locale: string;
49
- };
50
- interface I18nConfig {
51
- /**
52
- * Startup locale. Compute platform inputs OUTSIDE the plugin — it never
53
- * touches the DOM:
54
- *
55
- * ```ts
56
- * i18nPlugin({ locale: negotiateLocale(codes, navigator.languages) ?? 'en' })
57
- * ```
58
- *
59
- * May name a `loaders` pack: the fallback locale shows until the pack
60
- * arrives (effects fetch it at startup). Defaults to `fallbackLocale`.
61
- */
62
- locale?: string;
63
- /** The pack tried when a key misses the current locale. Default 'en'. */
64
- fallbackLocale?: string;
65
- /** Eagerly available packs. */
66
- locales?: Locale[];
67
- /**
68
- * Lazy packs: code → loader. `setLocale(code)` fetches on demand (in
69
- * effects), registers the pack, then switches. Until a lazy pack loads, a
70
- * locale switcher shows its code as the name — register eagerly (packs are
71
- * small) when you want native names in the switcher up front.
72
- */
73
- loaders?: Record<string, () => Promise<Locale>>;
74
- }
75
- interface TranslateOptions {
76
- /**
77
- * `{slot}` interpolation values. When `count` is a number and the key
78
- * resolves to a branch object, the branch is picked by CLDR plural
79
- * category (`Intl.PluralRules`), falling back to `other`:
80
- *
81
- * ```ts
82
- * // pages: { one: '{count} page', other: '{count} pages' }
83
- * t('pages', { params: { count: 1 } }) // '1 page'
84
- * ```
85
- */
86
- params?: Record<string, string | number>;
87
- /** Returned (interpolated) when the key misses every pack — instead of the key. */
88
- fallback?: string;
89
- }
90
- /** A locale as a switcher sees it — registered packs plus not-yet-loaded lazy ones. */
91
- interface LocaleInfo {
92
- readonly code: string;
93
- /** Native name; the bare code until a lazy pack has loaded. */
94
- readonly name: string;
95
- readonly dir: 'ltr' | 'rtl';
96
- readonly loaded: boolean;
97
- }
98
- interface I18nCapability {
99
- /** Translate a key: current locale → fallback locale → `options.fallback` → the key. */
100
- t(key: string, options?: TranslateOptions): string;
101
- locale(): string;
102
- /** Text direction of the CURRENT locale — wire to `dir=` on the shell. */
103
- dir(): 'ltr' | 'rtl';
104
- /** Every known locale (registered + lazy), in registration order. */
105
- locales(): LocaleInfo[];
106
- /** Code of the lazy pack being fetched right now, if any. */
107
- loading(): string | null;
108
- /** Switch locale; fetches the pack first when it's a lazy one. */
109
- setLocale(code: string): void;
110
- /** Register a pack at runtime (customer-supplied translations). */
111
- registerLocale(locale: Locale): void;
112
- }
113
- declare const I18nToken: import("@embedpdf/core").CapabilityToken<I18nCapability>;
114
- //#endregion
115
- export { I18nToken as a, TranslateOptions as c, I18nState as i, TranslationDictionary as l, I18nCapability as n, Locale as o, I18nConfig as r, LocaleInfo as s, I18nAction as t };
116
- //# sourceMappingURL=contract-DCt9Ikqu.d.cts.map
@@ -1,116 +0,0 @@
1
- //#region src/types.d.ts
2
- /**
3
- * @embedpdf/plugin-i18n — the contract.
4
- *
5
- * Translation is pure data over pure state: locale packs live IN the store
6
- * (not in a side-table), so `t()` is a pure read, every consumer is reactive
7
- * through the one kernel change stream, and the whole thing serializes (SSR,
8
- * snapshots, persist). Workspace-scoped, `requires: []`, never touches the
9
- * engine or the DOM — the capability is alive from `createKernel()`, before
10
- * a single WASM byte is fetched. The plugin ships NO strings: mechanism here,
11
- * content (locale packs) in the product that embeds it.
12
- */
13
- /** Nested tree of translation strings. Leaves interpolate `{param}` slots. */
14
- interface TranslationDictionary {
15
- readonly [key: string]: string | TranslationDictionary;
16
- }
17
- interface Locale {
18
- /** BCP-47 code: 'en', 'es', 'ar', 'zh-Hans'. */
19
- readonly code: string;
20
- /** Native display name: 'Español' — what a locale switcher shows. */
21
- readonly name: string;
22
- /** Text direction for viewer chrome. Defaults to 'ltr'. */
23
- readonly dir?: 'ltr' | 'rtl';
24
- readonly translations: TranslationDictionary;
25
- }
26
- interface I18nState {
27
- readonly locale: string;
28
- readonly fallbackLocale: string;
29
- /** Registered packs by code — the lookup table `t()` reads. */
30
- readonly locales: Readonly<Record<string, Locale>>;
31
- /** Code of a lazy pack currently being fetched (drives switcher spinners). */
32
- readonly loading: string | null;
33
- }
34
- type I18nAction =
35
- /** Switch to a REGISTERED locale. The reducer ignores unregistered codes —
36
- * loading a lazy pack goes through LOAD_STARTED (the capability routes). */
37
- {
38
- type: 'I18N/SET_LOCALE';
39
- locale: string;
40
- } | {
41
- type: 'I18N/REGISTER_LOCALE';
42
- locale: Locale;
43
- } | {
44
- type: 'I18N/LOAD_STARTED';
45
- locale: string;
46
- } | {
47
- type: 'I18N/LOAD_FAILED';
48
- locale: string;
49
- };
50
- interface I18nConfig {
51
- /**
52
- * Startup locale. Compute platform inputs OUTSIDE the plugin — it never
53
- * touches the DOM:
54
- *
55
- * ```ts
56
- * i18nPlugin({ locale: negotiateLocale(codes, navigator.languages) ?? 'en' })
57
- * ```
58
- *
59
- * May name a `loaders` pack: the fallback locale shows until the pack
60
- * arrives (effects fetch it at startup). Defaults to `fallbackLocale`.
61
- */
62
- locale?: string;
63
- /** The pack tried when a key misses the current locale. Default 'en'. */
64
- fallbackLocale?: string;
65
- /** Eagerly available packs. */
66
- locales?: Locale[];
67
- /**
68
- * Lazy packs: code → loader. `setLocale(code)` fetches on demand (in
69
- * effects), registers the pack, then switches. Until a lazy pack loads, a
70
- * locale switcher shows its code as the name — register eagerly (packs are
71
- * small) when you want native names in the switcher up front.
72
- */
73
- loaders?: Record<string, () => Promise<Locale>>;
74
- }
75
- interface TranslateOptions {
76
- /**
77
- * `{slot}` interpolation values. When `count` is a number and the key
78
- * resolves to a branch object, the branch is picked by CLDR plural
79
- * category (`Intl.PluralRules`), falling back to `other`:
80
- *
81
- * ```ts
82
- * // pages: { one: '{count} page', other: '{count} pages' }
83
- * t('pages', { params: { count: 1 } }) // '1 page'
84
- * ```
85
- */
86
- params?: Record<string, string | number>;
87
- /** Returned (interpolated) when the key misses every pack — instead of the key. */
88
- fallback?: string;
89
- }
90
- /** A locale as a switcher sees it — registered packs plus not-yet-loaded lazy ones. */
91
- interface LocaleInfo {
92
- readonly code: string;
93
- /** Native name; the bare code until a lazy pack has loaded. */
94
- readonly name: string;
95
- readonly dir: 'ltr' | 'rtl';
96
- readonly loaded: boolean;
97
- }
98
- interface I18nCapability {
99
- /** Translate a key: current locale → fallback locale → `options.fallback` → the key. */
100
- t(key: string, options?: TranslateOptions): string;
101
- locale(): string;
102
- /** Text direction of the CURRENT locale — wire to `dir=` on the shell. */
103
- dir(): 'ltr' | 'rtl';
104
- /** Every known locale (registered + lazy), in registration order. */
105
- locales(): LocaleInfo[];
106
- /** Code of the lazy pack being fetched right now, if any. */
107
- loading(): string | null;
108
- /** Switch locale; fetches the pack first when it's a lazy one. */
109
- setLocale(code: string): void;
110
- /** Register a pack at runtime (customer-supplied translations). */
111
- registerLocale(locale: Locale): void;
112
- }
113
- declare const I18nToken: import("@embedpdf/core").CapabilityToken<I18nCapability>;
114
- //#endregion
115
- export { I18nToken as a, TranslateOptions as c, I18nState as i, TranslationDictionary as l, I18nCapability as n, Locale as o, I18nConfig as r, LocaleInfo as s, I18nAction as t };
116
- //# sourceMappingURL=contract-DCt9Ikqu.d.ts.map