@embedpdf/plugin-i18n 2.14.4 → 3.0.0-next.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 (64) hide show
  1. package/LICENSE +192 -21
  2. package/dist/index.cjs +268 -2
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +159 -0
  5. package/dist/index.d.ts +159 -1
  6. package/dist/index.js +262 -397
  7. package/dist/index.js.map +1 -1
  8. package/package.json +18 -58
  9. package/dist/lib/actions.d.ts +0 -15
  10. package/dist/lib/i18n-plugin.d.ts +0 -28
  11. package/dist/lib/index.d.ts +0 -9
  12. package/dist/lib/locales/en.d.ts +0 -2
  13. package/dist/lib/locales/es.d.ts +0 -2
  14. package/dist/lib/locales/index.d.ts +0 -2
  15. package/dist/lib/manifest.d.ts +0 -4
  16. package/dist/lib/reducer.d.ts +0 -5
  17. package/dist/lib/types.d.ts +0 -104
  18. package/dist/preact/adapter.d.ts +0 -5
  19. package/dist/preact/core.d.ts +0 -1
  20. package/dist/preact/index.cjs +0 -2
  21. package/dist/preact/index.cjs.map +0 -1
  22. package/dist/preact/index.d.ts +0 -1
  23. package/dist/preact/index.js +0 -70
  24. package/dist/preact/index.js.map +0 -1
  25. package/dist/react/adapter.d.ts +0 -2
  26. package/dist/react/core.d.ts +0 -1
  27. package/dist/react/index.cjs +0 -2
  28. package/dist/react/index.cjs.map +0 -1
  29. package/dist/react/index.d.ts +0 -1
  30. package/dist/react/index.js +0 -69
  31. package/dist/react/index.js.map +0 -1
  32. package/dist/shared/components/index.d.ts +0 -1
  33. package/dist/shared/components/translate.d.ts +0 -35
  34. package/dist/shared/hooks/index.d.ts +0 -1
  35. package/dist/shared/hooks/use-i18n.d.ts +0 -43
  36. package/dist/shared/index.d.ts +0 -3
  37. package/dist/shared-preact/components/index.d.ts +0 -1
  38. package/dist/shared-preact/components/translate.d.ts +0 -35
  39. package/dist/shared-preact/hooks/index.d.ts +0 -1
  40. package/dist/shared-preact/hooks/use-i18n.d.ts +0 -43
  41. package/dist/shared-preact/index.d.ts +0 -3
  42. package/dist/shared-react/components/index.d.ts +0 -1
  43. package/dist/shared-react/components/translate.d.ts +0 -35
  44. package/dist/shared-react/hooks/index.d.ts +0 -1
  45. package/dist/shared-react/hooks/use-i18n.d.ts +0 -43
  46. package/dist/shared-react/index.d.ts +0 -3
  47. package/dist/svelte/components/Translate.svelte.d.ts +0 -9
  48. package/dist/svelte/components/index.d.ts +0 -1
  49. package/dist/svelte/hooks/index.d.ts +0 -1
  50. package/dist/svelte/hooks/use-i18n.svelte.d.ts +0 -41
  51. package/dist/svelte/index.cjs +0 -2
  52. package/dist/svelte/index.cjs.map +0 -1
  53. package/dist/svelte/index.d.ts +0 -3
  54. package/dist/svelte/index.js +0 -90
  55. package/dist/svelte/index.js.map +0 -1
  56. package/dist/vue/components/index.d.ts +0 -1
  57. package/dist/vue/components/translate.vue.d.ts +0 -9
  58. package/dist/vue/hooks/index.d.ts +0 -1
  59. package/dist/vue/hooks/use-i18n.d.ts +0 -30
  60. package/dist/vue/index.cjs +0 -2
  61. package/dist/vue/index.cjs.map +0 -1
  62. package/dist/vue/index.d.ts +0 -3
  63. package/dist/vue/index.js +0 -99
  64. package/dist/vue/index.js.map +0 -1
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/lib/locales/en.ts","../src/lib/locales/es.ts","../src/lib/manifest.ts","../src/lib/actions.ts","../src/lib/i18n-plugin.ts","../src/lib/reducer.ts","../src/lib/index.ts"],"sourcesContent":["import { Locale } from '../types';\n\nexport const enUS: Locale = {\n code: 'en',\n name: 'English',\n translations: {\n commands: {\n zoom: {\n in: 'Zoom In',\n out: 'Zoom Out',\n fitWidth: 'Fit to Width',\n fitPage: 'Fit to Page',\n automatic: 'Automatic',\n level: 'Zoom Level ({level}%)',\n inArea: 'Zoom In Area',\n },\n fullscreen: {\n enter: 'Enter Full Screen',\n exit: 'Exit Full Screen',\n },\n rotate: {\n clockwise: 'Rotate Clockwise',\n counterclockwise: 'Rotate Counter-Clockwise',\n },\n menu: 'Menu',\n sidebar: 'Sidebar',\n search: 'Search',\n comment: 'Comment',\n download: 'Download',\n print: 'Print',\n openFile: 'Open PDF',\n save: 'Save',\n settings: 'Settings',\n view: 'View',\n annotate: 'Annotate',\n shapes: 'Shapes',\n redact: 'Redact',\n fillAndSign: 'Fill and Sign',\n form: 'Form',\n pan: 'Pan',\n pointer: 'Pointer',\n undo: 'Undo',\n redo: 'Redo',\n copy: 'Copy',\n screenshot: 'Screenshot',\n nextPage: 'Next Page',\n previousPage: 'Previous Page',\n },\n },\n};\n","import { Locale } from '../types';\n\nexport const esES: Locale = {\n code: 'es',\n name: 'Español',\n translations: {\n commands: {\n zoom: {\n in: 'Acercar',\n out: 'Alejar',\n fitWidth: 'Ajustar al ancho',\n fitPage: 'Ajustar a la página',\n automatic: 'Automático',\n level: 'Nivel de zoom ({level}%)',\n inArea: 'Acercar área',\n },\n fullscreen: {\n enter: 'Pantalla completa',\n exit: 'Salir de pantalla completa',\n },\n rotate: {\n clockwise: 'Girar a la derecha',\n counterclockwise: 'Girar a la izquierda',\n },\n menu: 'Menú',\n sidebar: 'Barra lateral',\n search: 'Buscar',\n comment: 'Comentario',\n download: 'Descargar',\n print: 'Imprimir',\n openFile: 'Abrir PDF',\n save: 'Guardar',\n settings: 'Configuración',\n view: 'Ver',\n annotate: 'Anotar',\n shapes: 'Formas',\n redact: 'Redactar',\n fillAndSign: 'Rellenar y firmar',\n form: 'Formulario',\n pan: 'Desplazar',\n pointer: 'Puntero',\n undo: 'Deshacer',\n redo: 'Rehacer',\n copy: 'Copiar',\n screenshot: 'Captura de pantalla',\n nextPage: 'Página siguiente',\n previousPage: 'Página anterior',\n },\n },\n};\n","import { PluginManifest } from '@embedpdf/core';\nimport { I18nPluginConfig } from './types';\nimport { enUS, esES } from './locales';\n\nexport const I18N_PLUGIN_ID = 'i18n';\n\nexport const manifest: PluginManifest<I18nPluginConfig> = {\n id: I18N_PLUGIN_ID,\n name: 'I18n Plugin',\n version: '1.0.0',\n provides: ['i18n'],\n requires: [],\n optional: [],\n defaultConfig: {\n defaultLocale: 'en',\n locales: [enUS, esES],\n },\n};\n","import { Action } from '@embedpdf/core';\nimport { Locale, LocaleCode } from './types';\n\nexport const SET_LOCALE = 'I18N/SET_LOCALE';\nexport const REGISTER_LOCALE = 'I18N/REGISTER_LOCALE';\n\nexport interface SetLocaleAction extends Action {\n type: typeof SET_LOCALE;\n payload: LocaleCode;\n}\n\nexport interface RegisterLocaleAction extends Action {\n type: typeof REGISTER_LOCALE;\n payload: LocaleCode;\n}\n\nexport type I18nAction = SetLocaleAction | RegisterLocaleAction;\n\nexport const setLocale = (locale: LocaleCode): SetLocaleAction => ({\n type: SET_LOCALE,\n payload: locale,\n});\n\nexport const registerLocale = (locale: LocaleCode): RegisterLocaleAction => ({\n type: REGISTER_LOCALE,\n payload: locale,\n});\n","import {\n BasePlugin,\n PluginRegistry,\n StoreState,\n arePropsEqual,\n createEmitter,\n createScopedEmitter,\n} from '@embedpdf/core';\nimport {\n I18nCapability,\n I18nPluginConfig,\n I18nState,\n I18nScope,\n Locale,\n LocaleCode,\n LocaleChangeEvent,\n TranslationKey,\n TranslateOptions,\n ParamResolver,\n TranslationParamsChangedData,\n TranslationParamsChangedEvent,\n} from './types';\nimport {\n I18nAction,\n setLocale as setLocaleAction,\n registerLocale as registerLocaleAction,\n} from './actions';\n\nexport class I18nPlugin extends BasePlugin<\n I18nPluginConfig,\n I18nCapability,\n I18nState,\n I18nAction\n> {\n static readonly id = 'i18n' as const;\n\n private config: I18nPluginConfig;\n private locales = new Map<LocaleCode, Locale>();\n private paramResolvers = new Map<TranslationKey, ParamResolver>();\n\n // Cache resolved params per document per key\n // Map<documentId, Map<translationKey, resolvedParams>>\n private paramsCache = new Map<string, Map<TranslationKey, Record<string, string | number>>>();\n\n // Events\n private readonly localeChange$ = createEmitter<LocaleChangeEvent>();\n private readonly paramsChanged$ = createScopedEmitter<\n TranslationParamsChangedData,\n TranslationParamsChangedEvent,\n string\n >((documentId, data) => ({ documentId, ...data }), { cache: false });\n\n constructor(id: string, registry: PluginRegistry, config: I18nPluginConfig) {\n super(id, registry);\n\n this.config = config;\n\n // Register all provided locales\n config.locales.forEach((locale) => {\n this.locales.set(locale.code, locale);\n this.dispatch(registerLocaleAction(locale.code));\n });\n\n // Register param resolvers\n if (config.paramResolvers) {\n Object.entries(config.paramResolvers).forEach(([key, resolver]) => {\n this.paramResolvers.set(key, resolver);\n });\n }\n\n // Set initial locale\n this.dispatch(setLocaleAction(config.defaultLocale));\n\n // Subscribe to global store changes for param change detection\n this.registry.getStore().subscribe((_action, newState) => {\n this.detectParamChanges(newState);\n });\n }\n\n async initialize(): Promise<void> {\n this.logger.info('I18nPlugin', 'Initialize', 'I18n plugin initialized');\n }\n\n async destroy(): Promise<void> {\n this.localeChange$.clear();\n this.paramsChanged$.clear();\n this.paramResolvers.clear();\n this.paramsCache.clear();\n super.destroy();\n }\n\n protected override onDocumentClosed(documentId: string): void {\n // Clean up cache for closed document\n this.paramsCache.delete(documentId);\n this.paramsChanged$.clearScope(documentId);\n\n this.logger.debug(\n 'I18nPlugin',\n 'DocumentClosed',\n `Cleaned up params cache for document: ${documentId}`,\n );\n }\n\n protected buildCapability(): I18nCapability {\n return {\n t: (key, options) => this.translate(key, options),\n forDocument: (documentId) => this.createI18nScope(documentId),\n registerParamResolver: (key, resolver) => this.registerParamResolver(key, resolver),\n unregisterParamResolver: (key) => this.unregisterParamResolver(key),\n setLocale: (locale) => this.setLocale(locale),\n getLocale: () => this.state.currentLocale,\n getAvailableLocales: () => [...this.state.availableLocales],\n getLocaleInfo: (code) => this.locales.get(code) ?? null,\n registerLocale: (locale) => this.registerLocale(locale),\n hasLocale: (code) => this.locales.has(code),\n onLocaleChange: this.localeChange$.on,\n onParamsChanged: this.paramsChanged$.onGlobal,\n };\n }\n\n // ─────────────────────────────────────────────────────────\n // Document Scoping\n // ─────────────────────────────────────────────────────────\n\n private createI18nScope(documentId: string): I18nScope {\n return {\n t: (key, options) => this.translate(key, { documentId, ...options }),\n onParamsChanged: this.paramsChanged$.forScope(documentId),\n };\n }\n\n // ─────────────────────────────────────────────────────────\n // Translation Logic\n // ─────────────────────────────────────────────────────────\n\n private translate(key: TranslationKey, options?: TranslateOptions): string {\n const locale = this.locales.get(this.state.currentLocale);\n const fallbackLocale = this.config.fallbackLocale\n ? this.locales.get(this.config.fallbackLocale)\n : null;\n\n // Try current locale\n let value = this.getNestedValue(locale?.translations, key);\n\n // Try fallback locale\n if (!value && fallbackLocale) {\n value = this.getNestedValue(fallbackLocale.translations, key);\n }\n\n // If still not found, use fallback or return key\n if (!value) {\n if (options?.fallback) {\n // Use provided fallback string\n value = options.fallback;\n } else {\n this.logger.warn(\n 'I18nPlugin',\n 'MissingTranslation',\n `Translation not found for key: ${key}`,\n );\n return key;\n }\n }\n\n // Resolve params: explicit params take precedence over registered resolvers\n let params = options?.params;\n\n // If no explicit params and we have a resolver, try to resolve\n if (!params && this.paramResolvers.has(key)) {\n params = this.resolveParams(key, options?.documentId);\n }\n\n // Interpolate parameters (works for both found translations and fallback)\n return this.interpolate(value, params);\n }\n\n private resolveParams(\n key: TranslationKey,\n documentId?: string,\n ): Record<string, string | number> | undefined {\n const resolver = this.paramResolvers.get(key);\n if (!resolver) return undefined;\n\n const state = this.registry.getStore().getState();\n\n try {\n // Pass documentId (which may be undefined) to resolver\n return resolver({ state, documentId });\n } catch (error) {\n this.logger.error(\n 'I18nPlugin',\n 'ParamResolverError',\n `Error resolving params for key \"${key}\":`,\n error,\n );\n return undefined;\n }\n }\n\n private getNestedValue(obj: any, path: string): string | undefined {\n if (!obj) return undefined;\n\n const parts = path.split('.');\n let current = obj;\n\n for (const part of parts) {\n if (current === undefined || current === null) return undefined;\n current = current[part];\n }\n\n return typeof current === 'string' ? current : undefined;\n }\n\n private interpolate(str: string, params?: Record<string, string | number>): string {\n if (!params) return str;\n\n // Replace {key} with params[key]\n return str.replace(/\\{(\\w+)\\}/g, (match, key) => {\n const value = params[key];\n return value !== undefined ? String(value) : match;\n });\n }\n\n // ─────────────────────────────────────────────────────────\n // Smart Param Change Detection\n // ─────────────────────────────────────────────────────────\n\n private detectParamChanges(newState: StoreState<any>): void {\n // Get all documents from core state\n const documentIds = Object.keys(newState.core.documents);\n\n documentIds.forEach((documentId) => {\n this.detectDocumentParamChanges(documentId, newState);\n });\n }\n\n private detectDocumentParamChanges(documentId: string, newState: StoreState<any>): void {\n const previousCache = this.paramsCache.get(documentId);\n const changedKeys: TranslationKey[] = [];\n\n // Re-resolve all params that have resolvers\n this.paramResolvers.forEach((resolver, key) => {\n try {\n const newParams = resolver({ state: newState, documentId });\n const prevParams = previousCache?.get(key);\n\n // Compare params (deep equality for simplicity)\n if (!arePropsEqual(prevParams, newParams)) {\n changedKeys.push(key);\n\n // Update cache\n if (!this.paramsCache.has(documentId)) {\n this.paramsCache.set(documentId, new Map());\n }\n this.paramsCache.get(documentId)!.set(key, newParams);\n }\n } catch (error) {\n this.logger.error(\n 'I18nPlugin',\n 'ParamDetectionError',\n `Error detecting param changes for key \"${key}\":`,\n error,\n );\n }\n });\n\n // Emit event if any params changed\n if (changedKeys.length > 0) {\n this.paramsChanged$.emit(documentId, { changedKeys });\n\n this.logger.debug(\n 'I18nPlugin',\n 'ParamsChanged',\n `Translation params changed for document ${documentId}:`,\n changedKeys,\n );\n }\n }\n\n // ─────────────────────────────────────────────────────────\n // Param Resolver Management\n // ─────────────────────────────────────────────────────────\n\n private registerParamResolver(key: TranslationKey, resolver: ParamResolver): void {\n if (this.paramResolvers.has(key)) {\n this.logger.warn(\n 'I18nPlugin',\n 'ResolverOverwrite',\n `Param resolver for \"${key}\" already exists and will be overwritten`,\n );\n }\n\n this.paramResolvers.set(key, resolver);\n\n // Clear cache for this key in all documents\n this.paramsCache.forEach((docCache) => {\n docCache.delete(key);\n });\n\n this.logger.debug('I18nPlugin', 'ResolverRegistered', `Param resolver registered for: ${key}`);\n }\n\n private unregisterParamResolver(key: TranslationKey): void {\n if (this.paramResolvers.delete(key)) {\n // Clear cache for this key in all documents\n this.paramsCache.forEach((docCache) => {\n docCache.delete(key);\n });\n\n this.logger.debug(\n 'I18nPlugin',\n 'ResolverUnregistered',\n `Param resolver unregistered for: ${key}`,\n );\n }\n }\n\n // ─────────────────────────────────────────────────────────\n // Locale Management\n // ─────────────────────────────────────────────────────────\n\n private setLocale(locale: LocaleCode): void {\n if (!this.locales.has(locale)) {\n this.logger.warn('I18nPlugin', 'LocaleNotFound', `Locale '${locale}' is not registered`);\n return;\n }\n\n const previousLocale = this.state.currentLocale;\n if (previousLocale === locale) return;\n\n this.dispatch(setLocaleAction(locale));\n\n this.localeChange$.emit({\n previousLocale,\n currentLocale: locale,\n });\n\n this.logger.info('I18nPlugin', 'LocaleChanged', `Locale changed to: ${locale}`);\n }\n\n private registerLocale(locale: Locale): void {\n if (this.locales.has(locale.code)) {\n this.logger.warn(\n 'I18nPlugin',\n 'LocaleAlreadyRegistered',\n `Locale '${locale.code}' is already registered`,\n );\n return;\n }\n\n this.locales.set(locale.code, locale);\n this.dispatch(registerLocaleAction(locale.code));\n\n this.logger.info('I18nPlugin', 'LocaleRegistered', `Locale registered: ${locale.code}`);\n }\n}\n","import { Reducer } from '@embedpdf/core';\nimport { I18nState } from './types';\nimport { I18nAction, SET_LOCALE, REGISTER_LOCALE } from './actions';\n\nexport const initialState: I18nState = {\n currentLocale: 'en',\n availableLocales: [],\n};\n\nexport const i18nReducer: Reducer<I18nState, I18nAction> = (state = initialState, action) => {\n switch (action.type) {\n case SET_LOCALE: {\n const locale = action.payload;\n if (!state.availableLocales.includes(locale)) {\n console.warn(`I18nPlugin: Locale '${locale}' not available`);\n return state;\n }\n return {\n ...state,\n currentLocale: locale,\n };\n }\n\n case REGISTER_LOCALE: {\n const locale = action.payload;\n if (state.availableLocales.includes(locale)) {\n return state; // Already registered\n }\n return {\n ...state,\n availableLocales: [...state.availableLocales, locale],\n };\n }\n\n default:\n return state;\n }\n};\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, I18N_PLUGIN_ID } from './manifest';\nimport { I18nPluginConfig, I18nState } from './types';\nimport { I18nPlugin } from './i18n-plugin';\nimport { I18nAction } from './actions';\nimport { i18nReducer, initialState } from './reducer';\n\nexport const I18nPluginPackage: PluginPackage<I18nPlugin, I18nPluginConfig, I18nState, I18nAction> =\n {\n manifest,\n create: (registry, config) => new I18nPlugin(I18N_PLUGIN_ID, registry, config),\n reducer: i18nReducer,\n initialState,\n };\n\nexport * from './i18n-plugin';\nexport * from './types';\nexport * from './manifest';\nexport * from './locales';\n"],"names":["registerLocaleAction","setLocaleAction"],"mappings":";AAEO,MAAM,OAAe;AAAA,EAC1B,MAAM;AAAA,EACN,MAAM;AAAA,EACN,cAAc;AAAA,IACZ,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,KAAK;AAAA,QACL,UAAU;AAAA,QACV,SAAS;AAAA,QACT,WAAW;AAAA,QACX,OAAO;AAAA,QACP,QAAQ;AAAA,MAAA;AAAA,MAEV,YAAY;AAAA,QACV,OAAO;AAAA,QACP,MAAM;AAAA,MAAA;AAAA,MAER,QAAQ;AAAA,QACN,WAAW;AAAA,QACX,kBAAkB;AAAA,MAAA;AAAA,MAEpB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,UAAU;AAAA,MACV,OAAO;AAAA,MACP,UAAU;AAAA,MACV,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM;AAAA,MACN,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,MAAM;AAAA,MACN,KAAK;AAAA,MACL,SAAS;AAAA,MACT,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,cAAc;AAAA,IAAA;AAAA,EAChB;AAEJ;AC/CO,MAAM,OAAe;AAAA,EAC1B,MAAM;AAAA,EACN,MAAM;AAAA,EACN,cAAc;AAAA,IACZ,UAAU;AAAA,MACR,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,KAAK;AAAA,QACL,UAAU;AAAA,QACV,SAAS;AAAA,QACT,WAAW;AAAA,QACX,OAAO;AAAA,QACP,QAAQ;AAAA,MAAA;AAAA,MAEV,YAAY;AAAA,QACV,OAAO;AAAA,QACP,MAAM;AAAA,MAAA;AAAA,MAER,QAAQ;AAAA,QACN,WAAW;AAAA,QACX,kBAAkB;AAAA,MAAA;AAAA,MAEpB,MAAM;AAAA,MACN,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,UAAU;AAAA,MACV,OAAO;AAAA,MACP,UAAU;AAAA,MACV,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM;AAAA,MACN,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,MAAM;AAAA,MACN,KAAK;AAAA,MACL,SAAS;AAAA,MACT,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,cAAc;AAAA,IAAA;AAAA,EAChB;AAEJ;AC7CO,MAAM,iBAAiB;AAEvB,MAAM,WAA6C;AAAA,EACxD,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,SAAS;AAAA,EACT,UAAU,CAAC,MAAM;AAAA,EACjB,UAAU,CAAA;AAAA,EACV,UAAU,CAAA;AAAA,EACV,eAAe;AAAA,IACb,eAAe;AAAA,IACf,SAAS,CAAC,MAAM,IAAI;AAAA,EAAA;AAExB;ACdO,MAAM,aAAa;AACnB,MAAM,kBAAkB;AAcxB,MAAM,YAAY,CAAC,YAAyC;AAAA,EACjE,MAAM;AAAA,EACN,SAAS;AACX;AAEO,MAAM,iBAAiB,CAAC,YAA8C;AAAA,EAC3E,MAAM;AAAA,EACN,SAAS;AACX;ACEO,MAAM,cAAN,MAAM,oBAAmB,WAK9B;AAAA,EAmBA,YAAY,IAAY,UAA0B,QAA0B;AAC1E,UAAM,IAAI,QAAQ;AAhBpB,SAAQ,8BAAc,IAAA;AACtB,SAAQ,qCAAqB,IAAA;AAI7B,SAAQ,kCAAkB,IAAA;AAG1B,SAAiB,gBAAgB,cAAA;AACjC,SAAiB,iBAAiB,oBAIhC,CAAC,YAAY,UAAU,EAAE,YAAY,GAAG,KAAA,IAAS,EAAE,OAAO,OAAO;AAKjE,SAAK,SAAS;AAGd,WAAO,QAAQ,QAAQ,CAAC,WAAW;AACjC,WAAK,QAAQ,IAAI,OAAO,MAAM,MAAM;AACpC,WAAK,SAASA,eAAqB,OAAO,IAAI,CAAC;AAAA,IACjD,CAAC;AAGD,QAAI,OAAO,gBAAgB;AACzB,aAAO,QAAQ,OAAO,cAAc,EAAE,QAAQ,CAAC,CAAC,KAAK,QAAQ,MAAM;AACjE,aAAK,eAAe,IAAI,KAAK,QAAQ;AAAA,MACvC,CAAC;AAAA,IACH;AAGA,SAAK,SAASC,UAAgB,OAAO,aAAa,CAAC;AAGnD,SAAK,SAAS,SAAA,EAAW,UAAU,CAAC,SAAS,aAAa;AACxD,WAAK,mBAAmB,QAAQ;AAAA,IAClC,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,aAA4B;AAChC,SAAK,OAAO,KAAK,cAAc,cAAc,yBAAyB;AAAA,EACxE;AAAA,EAEA,MAAM,UAAyB;AAC7B,SAAK,cAAc,MAAA;AACnB,SAAK,eAAe,MAAA;AACpB,SAAK,eAAe,MAAA;AACpB,SAAK,YAAY,MAAA;AACjB,UAAM,QAAA;AAAA,EACR;AAAA,EAEmB,iBAAiB,YAA0B;AAE5D,SAAK,YAAY,OAAO,UAAU;AAClC,SAAK,eAAe,WAAW,UAAU;AAEzC,SAAK,OAAO;AAAA,MACV;AAAA,MACA;AAAA,MACA,yCAAyC,UAAU;AAAA,IAAA;AAAA,EAEvD;AAAA,EAEU,kBAAkC;AAC1C,WAAO;AAAA,MACL,GAAG,CAAC,KAAK,YAAY,KAAK,UAAU,KAAK,OAAO;AAAA,MAChD,aAAa,CAAC,eAAe,KAAK,gBAAgB,UAAU;AAAA,MAC5D,uBAAuB,CAAC,KAAK,aAAa,KAAK,sBAAsB,KAAK,QAAQ;AAAA,MAClF,yBAAyB,CAAC,QAAQ,KAAK,wBAAwB,GAAG;AAAA,MAClE,WAAW,CAAC,WAAW,KAAK,UAAU,MAAM;AAAA,MAC5C,WAAW,MAAM,KAAK,MAAM;AAAA,MAC5B,qBAAqB,MAAM,CAAC,GAAG,KAAK,MAAM,gBAAgB;AAAA,MAC1D,eAAe,CAAC,SAAS,KAAK,QAAQ,IAAI,IAAI,KAAK;AAAA,MACnD,gBAAgB,CAAC,WAAW,KAAK,eAAe,MAAM;AAAA,MACtD,WAAW,CAAC,SAAS,KAAK,QAAQ,IAAI,IAAI;AAAA,MAC1C,gBAAgB,KAAK,cAAc;AAAA,MACnC,iBAAiB,KAAK,eAAe;AAAA,IAAA;AAAA,EAEzC;AAAA;AAAA;AAAA;AAAA,EAMQ,gBAAgB,YAA+B;AACrD,WAAO;AAAA,MACL,GAAG,CAAC,KAAK,YAAY,KAAK,UAAU,KAAK,EAAE,YAAY,GAAG,SAAS;AAAA,MACnE,iBAAiB,KAAK,eAAe,SAAS,UAAU;AAAA,IAAA;AAAA,EAE5D;AAAA;AAAA;AAAA;AAAA,EAMQ,UAAU,KAAqB,SAAoC;AACzE,UAAM,SAAS,KAAK,QAAQ,IAAI,KAAK,MAAM,aAAa;AACxD,UAAM,iBAAiB,KAAK,OAAO,iBAC/B,KAAK,QAAQ,IAAI,KAAK,OAAO,cAAc,IAC3C;AAGJ,QAAI,QAAQ,KAAK,eAAe,iCAAQ,cAAc,GAAG;AAGzD,QAAI,CAAC,SAAS,gBAAgB;AAC5B,cAAQ,KAAK,eAAe,eAAe,cAAc,GAAG;AAAA,IAC9D;AAGA,QAAI,CAAC,OAAO;AACV,UAAI,mCAAS,UAAU;AAErB,gBAAQ,QAAQ;AAAA,MAClB,OAAO;AACL,aAAK,OAAO;AAAA,UACV;AAAA,UACA;AAAA,UACA,kCAAkC,GAAG;AAAA,QAAA;AAEvC,eAAO;AAAA,MACT;AAAA,IACF;AAGA,QAAI,SAAS,mCAAS;AAGtB,QAAI,CAAC,UAAU,KAAK,eAAe,IAAI,GAAG,GAAG;AAC3C,eAAS,KAAK,cAAc,KAAK,mCAAS,UAAU;AAAA,IACtD;AAGA,WAAO,KAAK,YAAY,OAAO,MAAM;AAAA,EACvC;AAAA,EAEQ,cACN,KACA,YAC6C;AAC7C,UAAM,WAAW,KAAK,eAAe,IAAI,GAAG;AAC5C,QAAI,CAAC,SAAU,QAAO;AAEtB,UAAM,QAAQ,KAAK,SAAS,SAAA,EAAW,SAAA;AAEvC,QAAI;AAEF,aAAO,SAAS,EAAE,OAAO,YAAY;AAAA,IACvC,SAAS,OAAO;AACd,WAAK,OAAO;AAAA,QACV;AAAA,QACA;AAAA,QACA,mCAAmC,GAAG;AAAA,QACtC;AAAA,MAAA;AAEF,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEQ,eAAe,KAAU,MAAkC;AACjE,QAAI,CAAC,IAAK,QAAO;AAEjB,UAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,QAAI,UAAU;AAEd,eAAW,QAAQ,OAAO;AACxB,UAAI,YAAY,UAAa,YAAY,KAAM,QAAO;AACtD,gBAAU,QAAQ,IAAI;AAAA,IACxB;AAEA,WAAO,OAAO,YAAY,WAAW,UAAU;AAAA,EACjD;AAAA,EAEQ,YAAY,KAAa,QAAkD;AACjF,QAAI,CAAC,OAAQ,QAAO;AAGpB,WAAO,IAAI,QAAQ,cAAc,CAAC,OAAO,QAAQ;AAC/C,YAAM,QAAQ,OAAO,GAAG;AACxB,aAAO,UAAU,SAAY,OAAO,KAAK,IAAI;AAAA,IAC/C,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAMQ,mBAAmB,UAAiC;AAE1D,UAAM,cAAc,OAAO,KAAK,SAAS,KAAK,SAAS;AAEvD,gBAAY,QAAQ,CAAC,eAAe;AAClC,WAAK,2BAA2B,YAAY,QAAQ;AAAA,IACtD,CAAC;AAAA,EACH;AAAA,EAEQ,2BAA2B,YAAoB,UAAiC;AACtF,UAAM,gBAAgB,KAAK,YAAY,IAAI,UAAU;AACrD,UAAM,cAAgC,CAAA;AAGtC,SAAK,eAAe,QAAQ,CAAC,UAAU,QAAQ;AAC7C,UAAI;AACF,cAAM,YAAY,SAAS,EAAE,OAAO,UAAU,YAAY;AAC1D,cAAM,aAAa,+CAAe,IAAI;AAGtC,YAAI,CAAC,cAAc,YAAY,SAAS,GAAG;AACzC,sBAAY,KAAK,GAAG;AAGpB,cAAI,CAAC,KAAK,YAAY,IAAI,UAAU,GAAG;AACrC,iBAAK,YAAY,IAAI,YAAY,oBAAI,KAAK;AAAA,UAC5C;AACA,eAAK,YAAY,IAAI,UAAU,EAAG,IAAI,KAAK,SAAS;AAAA,QACtD;AAAA,MACF,SAAS,OAAO;AACd,aAAK,OAAO;AAAA,UACV;AAAA,UACA;AAAA,UACA,0CAA0C,GAAG;AAAA,UAC7C;AAAA,QAAA;AAAA,MAEJ;AAAA,IACF,CAAC;AAGD,QAAI,YAAY,SAAS,GAAG;AAC1B,WAAK,eAAe,KAAK,YAAY,EAAE,aAAa;AAEpD,WAAK,OAAO;AAAA,QACV;AAAA,QACA;AAAA,QACA,2CAA2C,UAAU;AAAA,QACrD;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,sBAAsB,KAAqB,UAA+B;AAChF,QAAI,KAAK,eAAe,IAAI,GAAG,GAAG;AAChC,WAAK,OAAO;AAAA,QACV;AAAA,QACA;AAAA,QACA,uBAAuB,GAAG;AAAA,MAAA;AAAA,IAE9B;AAEA,SAAK,eAAe,IAAI,KAAK,QAAQ;AAGrC,SAAK,YAAY,QAAQ,CAAC,aAAa;AACrC,eAAS,OAAO,GAAG;AAAA,IACrB,CAAC;AAED,SAAK,OAAO,MAAM,cAAc,sBAAsB,kCAAkC,GAAG,EAAE;AAAA,EAC/F;AAAA,EAEQ,wBAAwB,KAA2B;AACzD,QAAI,KAAK,eAAe,OAAO,GAAG,GAAG;AAEnC,WAAK,YAAY,QAAQ,CAAC,aAAa;AACrC,iBAAS,OAAO,GAAG;AAAA,MACrB,CAAC;AAED,WAAK,OAAO;AAAA,QACV;AAAA,QACA;AAAA,QACA,oCAAoC,GAAG;AAAA,MAAA;AAAA,IAE3C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,UAAU,QAA0B;AAC1C,QAAI,CAAC,KAAK,QAAQ,IAAI,MAAM,GAAG;AAC7B,WAAK,OAAO,KAAK,cAAc,kBAAkB,WAAW,MAAM,qBAAqB;AACvF;AAAA,IACF;AAEA,UAAM,iBAAiB,KAAK,MAAM;AAClC,QAAI,mBAAmB,OAAQ;AAE/B,SAAK,SAASA,UAAgB,MAAM,CAAC;AAErC,SAAK,cAAc,KAAK;AAAA,MACtB;AAAA,MACA,eAAe;AAAA,IAAA,CAChB;AAED,SAAK,OAAO,KAAK,cAAc,iBAAiB,sBAAsB,MAAM,EAAE;AAAA,EAChF;AAAA,EAEQ,eAAe,QAAsB;AAC3C,QAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,GAAG;AACjC,WAAK,OAAO;AAAA,QACV;AAAA,QACA;AAAA,QACA,WAAW,OAAO,IAAI;AAAA,MAAA;AAExB;AAAA,IACF;AAEA,SAAK,QAAQ,IAAI,OAAO,MAAM,MAAM;AACpC,SAAK,SAASD,eAAqB,OAAO,IAAI,CAAC;AAE/C,SAAK,OAAO,KAAK,cAAc,oBAAoB,sBAAsB,OAAO,IAAI,EAAE;AAAA,EACxF;AACF;AAjUE,YAAgB,KAAK;AANhB,IAAM,aAAN;ACxBA,MAAM,eAA0B;AAAA,EACrC,eAAe;AAAA,EACf,kBAAkB,CAAA;AACpB;AAEO,MAAM,cAA8C,CAAC,QAAQ,cAAc,WAAW;AAC3F,UAAQ,OAAO,MAAA;AAAA,IACb,KAAK,YAAY;AACf,YAAM,SAAS,OAAO;AACtB,UAAI,CAAC,MAAM,iBAAiB,SAAS,MAAM,GAAG;AAC5C,gBAAQ,KAAK,uBAAuB,MAAM,iBAAiB;AAC3D,eAAO;AAAA,MACT;AACA,aAAO;AAAA,QACL,GAAG;AAAA,QACH,eAAe;AAAA,MAAA;AAAA,IAEnB;AAAA,IAEA,KAAK,iBAAiB;AACpB,YAAM,SAAS,OAAO;AACtB,UAAI,MAAM,iBAAiB,SAAS,MAAM,GAAG;AAC3C,eAAO;AAAA,MACT;AACA,aAAO;AAAA,QACL,GAAG;AAAA,QACH,kBAAkB,CAAC,GAAG,MAAM,kBAAkB,MAAM;AAAA,MAAA;AAAA,IAExD;AAAA,IAEA;AACE,aAAO;AAAA,EAAA;AAEb;AC9BO,MAAM,oBACX;AAAA,EACE;AAAA,EACA,QAAQ,CAAC,UAAU,WAAW,IAAI,WAAW,gBAAgB,UAAU,MAAM;AAAA,EAC7E,SAAS;AAAA,EACT;AACF;"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/translate.ts","../src/capability.ts","../src/effects.ts","../src/reducer.ts","../src/types.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 { 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","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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"}
package/package.json CHANGED
@@ -1,81 +1,41 @@
1
1
  {
2
2
  "name": "@embedpdf/plugin-i18n",
3
- "version": "2.14.4",
3
+ "version": "3.0.0-next.0",
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.",
4
6
  "type": "module",
5
- "license": "MIT",
6
- "main": "./dist/index.cjs",
7
+ "main": "./dist/index.js",
7
8
  "module": "./dist/index.js",
8
9
  "types": "./dist/index.d.ts",
9
10
  "exports": {
10
11
  ".": {
11
- "types": "./dist/index.d.ts",
12
12
  "import": "./dist/index.js",
13
13
  "require": "./dist/index.cjs"
14
14
  },
15
- "./preact": {
16
- "types": "./dist/preact/index.d.ts",
17
- "import": "./dist/preact/index.js",
18
- "require": "./dist/preact/index.cjs"
19
- },
20
- "./react": {
21
- "types": "./dist/react/index.d.ts",
22
- "import": "./dist/react/index.js",
23
- "require": "./dist/react/index.cjs"
24
- },
25
- "./vue": {
26
- "types": "./dist/vue/index.d.ts",
27
- "import": "./dist/vue/index.js",
28
- "require": "./dist/vue/index.cjs"
29
- },
30
- "./svelte": {
31
- "types": "./dist/svelte/index.d.ts",
32
- "svelte": "./dist/svelte/index.js",
33
- "import": "./dist/svelte/index.js",
34
- "require": "./dist/svelte/index.cjs"
35
- }
15
+ "./package.json": "./package.json"
36
16
  },
17
+ "files": [
18
+ "dist"
19
+ ],
37
20
  "dependencies": {
38
- "@embedpdf/models": "2.14.4"
21
+ "@embedpdf/core": "3.0.0-next.0"
39
22
  },
40
23
  "devDependencies": {
41
- "@types/react": "^18.2.0",
42
24
  "typescript": "^5.0.0",
43
- "@embedpdf/core": "2.14.4",
44
- "@embedpdf/build": "1.1.0"
25
+ "vitest": "^2.1.9",
26
+ "@embedpdf/tooling-build": "0.0.0"
45
27
  },
46
- "peerDependencies": {
47
- "react": ">=16.8.0",
48
- "react-dom": ">=16.8.0",
49
- "preact": "^10.26.4",
50
- "vue": ">=3.2.0",
51
- "svelte": ">=5 <6",
52
- "@embedpdf/core": "2.14.4"
53
- },
54
- "files": [
55
- "dist",
56
- "README.md"
57
- ],
28
+ "license": "Apache-2.0",
29
+ "sideEffects": false,
58
30
  "repository": {
59
31
  "type": "git",
60
- "url": "https://github.com/embedpdf/embed-pdf-viewer",
61
- "directory": "packages/plugin-i18n"
62
- },
63
- "homepage": "https://www.embedpdf.com/docs",
64
- "bugs": {
65
- "url": "https://github.com/embedpdf/embed-pdf-viewer/issues"
66
- },
67
- "publishConfig": {
68
- "access": "public"
32
+ "url": "git+https://github.com/embedpdf/embed-pdf-viewer.git",
33
+ "directory": "packages/plugin/i18n"
69
34
  },
70
35
  "scripts": {
71
- "build:base": "vite build --mode base",
72
- "build:react": "vite build --mode react",
73
- "build:preact": "vite build --mode preact",
74
- "build:vue": "vite build --mode vue",
75
- "build:svelte": "vite build --mode svelte",
76
- "build": "pnpm run clean && concurrently -c auto -n base,react,preact,vue,svelte \"vite build --mode base\" \"vite build --mode react\" \"vite build --mode preact\" \"vite build --mode vue\" \"vite build --mode svelte\"",
36
+ "build": "epdf-build",
77
37
  "clean": "rimraf dist",
78
- "lint": "eslint src --color",
79
- "lint:fix": "eslint src --color --fix"
38
+ "typecheck": "tsc -p tsconfig.json --noEmit",
39
+ "test": "vitest run"
80
40
  }
81
41
  }
@@ -1,15 +0,0 @@
1
- import { Action } from '@embedpdf/core';
2
- import { LocaleCode } from './types';
3
- export declare const SET_LOCALE = "I18N/SET_LOCALE";
4
- export declare const REGISTER_LOCALE = "I18N/REGISTER_LOCALE";
5
- export interface SetLocaleAction extends Action {
6
- type: typeof SET_LOCALE;
7
- payload: LocaleCode;
8
- }
9
- export interface RegisterLocaleAction extends Action {
10
- type: typeof REGISTER_LOCALE;
11
- payload: LocaleCode;
12
- }
13
- export type I18nAction = SetLocaleAction | RegisterLocaleAction;
14
- export declare const setLocale: (locale: LocaleCode) => SetLocaleAction;
15
- export declare const registerLocale: (locale: LocaleCode) => RegisterLocaleAction;
@@ -1,28 +0,0 @@
1
- import { BasePlugin, PluginRegistry } from '@embedpdf/core';
2
- import { I18nCapability, I18nPluginConfig, I18nState } from './types';
3
- import { I18nAction } from './actions';
4
- export declare class I18nPlugin extends BasePlugin<I18nPluginConfig, I18nCapability, I18nState, I18nAction> {
5
- static readonly id: "i18n";
6
- private config;
7
- private locales;
8
- private paramResolvers;
9
- private paramsCache;
10
- private readonly localeChange$;
11
- private readonly paramsChanged$;
12
- constructor(id: string, registry: PluginRegistry, config: I18nPluginConfig);
13
- initialize(): Promise<void>;
14
- destroy(): Promise<void>;
15
- protected onDocumentClosed(documentId: string): void;
16
- protected buildCapability(): I18nCapability;
17
- private createI18nScope;
18
- private translate;
19
- private resolveParams;
20
- private getNestedValue;
21
- private interpolate;
22
- private detectParamChanges;
23
- private detectDocumentParamChanges;
24
- private registerParamResolver;
25
- private unregisterParamResolver;
26
- private setLocale;
27
- private registerLocale;
28
- }
@@ -1,9 +0,0 @@
1
- import { PluginPackage } from '@embedpdf/core';
2
- import { I18nPluginConfig, I18nState } from './types';
3
- import { I18nPlugin } from './i18n-plugin';
4
- import { I18nAction } from './actions';
5
- export declare const I18nPluginPackage: PluginPackage<I18nPlugin, I18nPluginConfig, I18nState, I18nAction>;
6
- export * from './i18n-plugin';
7
- export * from './types';
8
- export * from './manifest';
9
- export * from './locales';
@@ -1,2 +0,0 @@
1
- import { Locale } from '../types';
2
- export declare const enUS: Locale;
@@ -1,2 +0,0 @@
1
- import { Locale } from '../types';
2
- export declare const esES: Locale;
@@ -1,2 +0,0 @@
1
- export * from './en';
2
- export * from './es';
@@ -1,4 +0,0 @@
1
- import { PluginManifest } from '@embedpdf/core';
2
- import { I18nPluginConfig } from './types';
3
- export declare const I18N_PLUGIN_ID = "i18n";
4
- export declare const manifest: PluginManifest<I18nPluginConfig>;
@@ -1,5 +0,0 @@
1
- import { Reducer } from '@embedpdf/core';
2
- import { I18nState } from './types';
3
- import { I18nAction } from './actions';
4
- export declare const initialState: I18nState;
5
- export declare const i18nReducer: Reducer<I18nState, I18nAction>;
@@ -1,104 +0,0 @@
1
- import { BasePluginConfig, EventHook } from '@embedpdf/core';
2
- export type TranslationKey = string;
3
- export type LocaleCode = string;
4
- export type ParamResolver<TStore = any> = (context: {
5
- state: TStore;
6
- documentId?: string;
7
- }) => Record<string, string | number>;
8
- export interface TranslateOptions {
9
- documentId?: string;
10
- params?: Record<string, string | number>;
11
- fallback?: string;
12
- }
13
- export type ScopedTranslateOptions = Omit<TranslateOptions, 'documentId'>;
14
- export type ParamResolvers<TStore = any> = Record<TranslationKey, ParamResolver<TStore>>;
15
- export interface TranslationDictionary {
16
- [key: string]: string | TranslationDictionary;
17
- }
18
- export interface Locale {
19
- code: LocaleCode;
20
- name: string;
21
- translations: TranslationDictionary;
22
- }
23
- export interface I18nPluginConfig extends BasePluginConfig {
24
- defaultLocale: LocaleCode;
25
- fallbackLocale?: LocaleCode;
26
- locales: Locale[];
27
- paramResolvers?: ParamResolvers;
28
- }
29
- export interface I18nState {
30
- currentLocale: LocaleCode;
31
- availableLocales: LocaleCode[];
32
- }
33
- export interface LocaleChangeEvent {
34
- previousLocale: LocaleCode;
35
- currentLocale: LocaleCode;
36
- }
37
- export interface TranslationParamsChangedData {
38
- changedKeys: TranslationKey[];
39
- }
40
- export interface TranslationParamsChangedEvent {
41
- documentId: string;
42
- changedKeys: TranslationKey[];
43
- }
44
- export interface I18nScope {
45
- /**
46
- * Translate a key for this document
47
- * @param key - Translation key
48
- * @param options - Optional translation options (params, fallback)
49
- *
50
- * @example
51
- * const scoped = i18n.forDocument(documentId);
52
- * scoped.t('page.title')
53
- * scoped.t('page.count', { params: { count: 5 } })
54
- * scoped.t('unknown.key', { fallback: 'Default Text' })
55
- */
56
- t(key: TranslationKey, options?: ScopedTranslateOptions): string;
57
- /**
58
- * Event when translation params change for this document
59
- */
60
- onParamsChanged: EventHook<TranslationParamsChangedData>;
61
- }
62
- export interface I18nCapability {
63
- /**
64
- * Translate a key with optional context
65
- * If a param resolver is registered for this key, it will be called automatically
66
- *
67
- * @param key - Translation key
68
- * @param options - Optional translation options (documentId, params)
69
- *
70
- * @example
71
- * // Simple global translation
72
- * i18n.t('loading.viewer')
73
- *
74
- * // Global translation with params
75
- * i18n.t('document.total', { params: { amount: 5 } })
76
- *
77
- * // Document-scoped with param resolver
78
- * i18n.t('page.current', { documentId })
79
- *
80
- * // Document-scoped with explicit params
81
- * i18n.t('welcome', { documentId, params: { name: 'User' } })
82
- */
83
- t(key: TranslationKey, options?: TranslateOptions): string;
84
- /**
85
- * Get document-scoped API
86
- */
87
- forDocument(documentId: string): I18nScope;
88
- /**
89
- * Register a param resolver dynamically
90
- */
91
- registerParamResolver(key: TranslationKey, resolver: ParamResolver): void;
92
- /**
93
- * Unregister a param resolver
94
- */
95
- unregisterParamResolver(key: TranslationKey): void;
96
- setLocale(locale: LocaleCode): void;
97
- getLocale(): LocaleCode;
98
- getAvailableLocales(): LocaleCode[];
99
- getLocaleInfo(code: LocaleCode): Locale | null;
100
- registerLocale(locale: Locale): void;
101
- hasLocale(code: LocaleCode): boolean;
102
- onLocaleChange: EventHook<LocaleChangeEvent>;
103
- onParamsChanged: EventHook<TranslationParamsChangedEvent>;
104
- }
@@ -1,5 +0,0 @@
1
- export { Fragment } from 'preact';
2
- export { useEffect, useRef, useState, useCallback, useMemo, useReducer } from 'preact/hooks';
3
- export type { ComponentChildren as ReactNode } from 'preact';
4
- export type CSSProperties = import('preact').JSX.CSSProperties;
5
- export type HTMLAttributes<T = any> = import('preact').JSX.HTMLAttributes<T extends EventTarget ? T : never>;
@@ -1 +0,0 @@
1
- export * from '@embedpdf/core/preact';
@@ -1,2 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@embedpdf/core/preact"),r=require("@embedpdf/plugin-i18n");require("preact");const t=require("preact/hooks"),a=()=>e.useCapability(r.I18nPlugin.id),n=e=>{const{provides:r}=a(),[,n]=t.useReducer(e=>e+1,0),o=t.useCallback((t,a)=>r?r.t(t,{documentId:e,params:null==a?void 0:a.params,fallback:null==a?void 0:a.fallback}):(null==a?void 0:a.fallback)??t,[r,e]);return t.useEffect(()=>{if(!r)return;const t=r.onLocaleChange(()=>{n()}),a=e?r.forDocument(e).onParamsChanged(()=>{n()}):r.onParamsChanged(()=>{n()});return()=>{t(),a()}},[r,e]),{translate:o,locale:(null==r?void 0:r.getLocale())??"en"}},o=(e,r,t)=>{const{translate:a}=n(t);return a(e,r)};exports.Translate=function({k:e,params:r,fallback:t,documentId:a,children:n}){const l=o(e,{params:r,fallback:t},a);return n?n(l):l},exports.useI18nCapability=a,exports.useI18nPlugin=()=>e.usePlugin(r.I18nPlugin.id),exports.useLocale=()=>{const{provides:e}=a(),[r,n]=t.useState(()=>(null==e?void 0:e.getLocale())??"en");return t.useEffect(()=>{if(!e)return;const r=e.onLocaleChange(({currentLocale:e})=>{n(e)});return n(e.getLocale()),r},[e]),r},exports.useTranslation=o,exports.useTranslations=n,Object.keys(r).forEach(e=>{"default"===e||Object.prototype.hasOwnProperty.call(exports,e)||Object.defineProperty(exports,e,{enumerable:!0,get:()=>r[e]})});
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","sources":["../../src/shared/hooks/use-i18n.ts","../../src/shared/components/translate.tsx"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/@framework';\nimport { I18nPlugin } from '@embedpdf/plugin-i18n';\nimport { useState, useEffect, useCallback, useReducer } from '@framework';\n\nexport const useI18nCapability = () => useCapability<I18nPlugin>(I18nPlugin.id);\nexport const useI18nPlugin = () => usePlugin<I18nPlugin>(I18nPlugin.id);\n\n/**\n * Hook to get a translate function for a component\n * Automatically updates all translations on locale or param changes\n *\n * @param documentId - Optional document ID for document-scoped translations\n * @returns translate function and current locale\n *\n * @example\n * const { translate } = useTranslations(documentId);\n * return (\n * <div>\n * <h1>{translate('page.title')}</h1>\n * <p>{translate('page.count', { params: { count: 5 } })}</p>\n * <p>{translate('unknown.key', { fallback: 'Default Text' })}</p>\n * </div>\n * );\n */\nexport const useTranslations = (documentId?: string) => {\n const { provides } = useI18nCapability();\n const [, forceUpdate] = useReducer((x) => x + 1, 0);\n\n // Create a stable translate function with options support\n const translate = useCallback(\n (\n key: string,\n options?: {\n params?: Record<string, string | number>;\n fallback?: string;\n },\n ): string => {\n if (!provides) return options?.fallback ?? key;\n return provides.t(key, { documentId, params: options?.params, fallback: options?.fallback });\n },\n [provides, documentId],\n );\n\n useEffect(() => {\n if (!provides) return;\n\n // Update on locale change (all translations need to refresh)\n const unsubscribeLocale = provides.onLocaleChange(() => {\n forceUpdate();\n });\n\n // Update on params change (for keys with param resolvers)\n const unsubscribeParams = documentId\n ? provides.forDocument(documentId).onParamsChanged(() => {\n forceUpdate(); // Re-render component when any params change\n })\n : provides.onParamsChanged(() => {\n forceUpdate(); // Global params change\n });\n\n return () => {\n unsubscribeLocale();\n unsubscribeParams();\n };\n }, [provides, documentId]);\n\n return {\n translate,\n locale: provides?.getLocale() ?? 'en',\n };\n};\n\n// Keep the old hook for single-key translations (useful for derived state)\nexport const useTranslation = (\n key: string,\n options?: {\n params?: Record<string, string | number>;\n fallback?: string;\n },\n documentId?: string,\n) => {\n const { translate } = useTranslations(documentId);\n return translate(key, options);\n};\n\n/**\n * Hook to get current locale\n */\nexport const useLocale = () => {\n const { provides } = useI18nCapability();\n const [locale, setLocale] = useState<string>(() => provides?.getLocale() ?? 'en');\n\n useEffect(() => {\n if (!provides) return;\n\n const unsubscribe = provides.onLocaleChange(({ currentLocale }) => {\n setLocale(currentLocale);\n });\n\n setLocale(provides.getLocale());\n\n return unsubscribe;\n }, [provides]);\n\n return locale;\n};\n","import { ReactNode } from '@framework';\nimport { useTranslation } from '../hooks';\n\ninterface TranslateProps {\n k: string; // translation key\n params?: Record<string, string | number>;\n fallback?: string; // Fallback string if translation not found\n documentId?: string; // Optional document ID for document-scoped translations\n children?: (translation: string) => ReactNode;\n}\n\n/**\n * Component for inline translation\n *\n * @example\n * // Global translation\n * <Translate k=\"loading.viewer\" />\n *\n * @example\n * // With explicit params\n * <Translate k=\"zoom.level\" params={{ level: 150 }} />\n *\n * @example\n * // With fallback\n * <Translate k=\"unknown.key\" fallback=\"Default Text\" />\n *\n * @example\n * // Document-scoped (uses param resolvers)\n * <Translate k=\"page.current\" documentId={documentId} />\n *\n * @example\n * // With render prop\n * <Translate k=\"zoom.level\" params={{ level: 150 }}>\n * {(text) => <span className=\"font-bold\">{text}</span>}\n * </Translate>\n */\nexport function Translate({ k, params, fallback, documentId, children }: TranslateProps) {\n const translation = useTranslation(k, { params, fallback }, documentId);\n\n if (children) {\n return children(translation);\n }\n\n return translation;\n}\n"],"names":["useI18nCapability","useCapability","I18nPlugin","id","useTranslations","documentId","provides","forceUpdate","useReducer","x","translate","useCallback","key","options","t","params","fallback","useEffect","unsubscribeLocale","onLocaleChange","unsubscribeParams","forDocument","onParamsChanged","locale","getLocale","useTranslation","k","children","translation","usePlugin","setLocale","useState","unsubscribe","currentLocale"],"mappings":"8MAIaA,EAAoB,IAAMC,gBAA0BC,EAAAA,WAAWC,IAoB/DC,EAAmBC,IAC9B,MAAMC,SAAEA,GAAaN,KACf,CAAGO,GAAeC,EAAAA,WAAYC,GAAMA,EAAI,EAAG,GAG3CC,EAAYC,EAAAA,YAChB,CACEC,EACAC,IAKKP,EACEA,EAASQ,EAAEF,EAAK,CAAEP,aAAYU,OAAQ,MAAAF,OAAA,EAAAA,EAASE,OAAQC,SAAU,MAAAH,OAAA,EAAAA,EAASG,YAD3D,MAAAH,OAAA,EAAAA,EAASG,WAAYJ,EAG7C,CAACN,EAAUD,IA0Bb,OAvBAY,EAAAA,UAAU,KACR,IAAKX,EAAU,OAGf,MAAMY,EAAoBZ,EAASa,eAAe,KAChDZ,MAIIa,EAAoBf,EACtBC,EAASe,YAAYhB,GAAYiB,gBAAgB,KAC/Cf,MAEFD,EAASgB,gBAAgB,KACvBf,MAGN,MAAO,KACLW,IACAE,MAED,CAACd,EAAUD,IAEP,CACLK,YACAa,cAAQjB,WAAUkB,cAAe,OAKxBC,EAAiB,CAC5Bb,EACAC,EAIAR,KAEA,MAAMK,UAAEA,GAAcN,EAAgBC,GACtC,OAAOK,EAAUE,EAAKC,sBC9CjB,UAAmBa,EAAEA,EAAAX,OAAGA,WAAQC,EAAAX,WAAUA,EAAAsB,SAAYA,IAC3D,MAAMC,EAAcH,EAAeC,EAAG,CAAEX,SAAQC,YAAYX,GAE5D,OAAIsB,EACKA,EAASC,GAGXA,CACT,oDDvC6B,IAAMC,YAAsB3B,EAAAA,WAAWC,sBAmF3C,KACvB,MAAMG,SAAEA,GAAaN,KACduB,EAAQO,GAAaC,EAAAA,SAAiB,KAAM,MAAAzB,OAAA,EAAAA,EAAUkB,cAAe,MAc5E,OAZAP,EAAAA,UAAU,KACR,IAAKX,EAAU,OAEf,MAAM0B,EAAc1B,EAASa,eAAe,EAAGc,oBAC7CH,EAAUG,KAKZ,OAFAH,EAAUxB,EAASkB,aAEZQ,GACN,CAAC1B,IAEGiB"}
@@ -1 +0,0 @@
1
- export * from '../shared-preact';
@@ -1,70 +0,0 @@
1
- import { useCapability, usePlugin } from "@embedpdf/core/preact";
2
- import { I18nPlugin } from "@embedpdf/plugin-i18n";
3
- export * from "@embedpdf/plugin-i18n";
4
- import "preact";
5
- import { useState, useEffect, useReducer, useCallback } from "preact/hooks";
6
- const useI18nCapability = () => useCapability(I18nPlugin.id);
7
- const useI18nPlugin = () => usePlugin(I18nPlugin.id);
8
- const useTranslations = (documentId) => {
9
- const { provides } = useI18nCapability();
10
- const [, forceUpdate] = useReducer((x) => x + 1, 0);
11
- const translate = useCallback(
12
- (key, options) => {
13
- if (!provides) return (options == null ? void 0 : options.fallback) ?? key;
14
- return provides.t(key, { documentId, params: options == null ? void 0 : options.params, fallback: options == null ? void 0 : options.fallback });
15
- },
16
- [provides, documentId]
17
- );
18
- useEffect(() => {
19
- if (!provides) return;
20
- const unsubscribeLocale = provides.onLocaleChange(() => {
21
- forceUpdate();
22
- });
23
- const unsubscribeParams = documentId ? provides.forDocument(documentId).onParamsChanged(() => {
24
- forceUpdate();
25
- }) : provides.onParamsChanged(() => {
26
- forceUpdate();
27
- });
28
- return () => {
29
- unsubscribeLocale();
30
- unsubscribeParams();
31
- };
32
- }, [provides, documentId]);
33
- return {
34
- translate,
35
- locale: (provides == null ? void 0 : provides.getLocale()) ?? "en"
36
- };
37
- };
38
- const useTranslation = (key, options, documentId) => {
39
- const { translate } = useTranslations(documentId);
40
- return translate(key, options);
41
- };
42
- const useLocale = () => {
43
- const { provides } = useI18nCapability();
44
- const [locale, setLocale] = useState(() => (provides == null ? void 0 : provides.getLocale()) ?? "en");
45
- useEffect(() => {
46
- if (!provides) return;
47
- const unsubscribe = provides.onLocaleChange(({ currentLocale }) => {
48
- setLocale(currentLocale);
49
- });
50
- setLocale(provides.getLocale());
51
- return unsubscribe;
52
- }, [provides]);
53
- return locale;
54
- };
55
- function Translate({ k, params, fallback, documentId, children }) {
56
- const translation = useTranslation(k, { params, fallback }, documentId);
57
- if (children) {
58
- return children(translation);
59
- }
60
- return translation;
61
- }
62
- export {
63
- Translate,
64
- useI18nCapability,
65
- useI18nPlugin,
66
- useLocale,
67
- useTranslation,
68
- useTranslations
69
- };
70
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":["../../src/shared/hooks/use-i18n.ts","../../src/shared/components/translate.tsx"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/@framework';\nimport { I18nPlugin } from '@embedpdf/plugin-i18n';\nimport { useState, useEffect, useCallback, useReducer } from '@framework';\n\nexport const useI18nCapability = () => useCapability<I18nPlugin>(I18nPlugin.id);\nexport const useI18nPlugin = () => usePlugin<I18nPlugin>(I18nPlugin.id);\n\n/**\n * Hook to get a translate function for a component\n * Automatically updates all translations on locale or param changes\n *\n * @param documentId - Optional document ID for document-scoped translations\n * @returns translate function and current locale\n *\n * @example\n * const { translate } = useTranslations(documentId);\n * return (\n * <div>\n * <h1>{translate('page.title')}</h1>\n * <p>{translate('page.count', { params: { count: 5 } })}</p>\n * <p>{translate('unknown.key', { fallback: 'Default Text' })}</p>\n * </div>\n * );\n */\nexport const useTranslations = (documentId?: string) => {\n const { provides } = useI18nCapability();\n const [, forceUpdate] = useReducer((x) => x + 1, 0);\n\n // Create a stable translate function with options support\n const translate = useCallback(\n (\n key: string,\n options?: {\n params?: Record<string, string | number>;\n fallback?: string;\n },\n ): string => {\n if (!provides) return options?.fallback ?? key;\n return provides.t(key, { documentId, params: options?.params, fallback: options?.fallback });\n },\n [provides, documentId],\n );\n\n useEffect(() => {\n if (!provides) return;\n\n // Update on locale change (all translations need to refresh)\n const unsubscribeLocale = provides.onLocaleChange(() => {\n forceUpdate();\n });\n\n // Update on params change (for keys with param resolvers)\n const unsubscribeParams = documentId\n ? provides.forDocument(documentId).onParamsChanged(() => {\n forceUpdate(); // Re-render component when any params change\n })\n : provides.onParamsChanged(() => {\n forceUpdate(); // Global params change\n });\n\n return () => {\n unsubscribeLocale();\n unsubscribeParams();\n };\n }, [provides, documentId]);\n\n return {\n translate,\n locale: provides?.getLocale() ?? 'en',\n };\n};\n\n// Keep the old hook for single-key translations (useful for derived state)\nexport const useTranslation = (\n key: string,\n options?: {\n params?: Record<string, string | number>;\n fallback?: string;\n },\n documentId?: string,\n) => {\n const { translate } = useTranslations(documentId);\n return translate(key, options);\n};\n\n/**\n * Hook to get current locale\n */\nexport const useLocale = () => {\n const { provides } = useI18nCapability();\n const [locale, setLocale] = useState<string>(() => provides?.getLocale() ?? 'en');\n\n useEffect(() => {\n if (!provides) return;\n\n const unsubscribe = provides.onLocaleChange(({ currentLocale }) => {\n setLocale(currentLocale);\n });\n\n setLocale(provides.getLocale());\n\n return unsubscribe;\n }, [provides]);\n\n return locale;\n};\n","import { ReactNode } from '@framework';\nimport { useTranslation } from '../hooks';\n\ninterface TranslateProps {\n k: string; // translation key\n params?: Record<string, string | number>;\n fallback?: string; // Fallback string if translation not found\n documentId?: string; // Optional document ID for document-scoped translations\n children?: (translation: string) => ReactNode;\n}\n\n/**\n * Component for inline translation\n *\n * @example\n * // Global translation\n * <Translate k=\"loading.viewer\" />\n *\n * @example\n * // With explicit params\n * <Translate k=\"zoom.level\" params={{ level: 150 }} />\n *\n * @example\n * // With fallback\n * <Translate k=\"unknown.key\" fallback=\"Default Text\" />\n *\n * @example\n * // Document-scoped (uses param resolvers)\n * <Translate k=\"page.current\" documentId={documentId} />\n *\n * @example\n * // With render prop\n * <Translate k=\"zoom.level\" params={{ level: 150 }}>\n * {(text) => <span className=\"font-bold\">{text}</span>}\n * </Translate>\n */\nexport function Translate({ k, params, fallback, documentId, children }: TranslateProps) {\n const translation = useTranslation(k, { params, fallback }, documentId);\n\n if (children) {\n return children(translation);\n }\n\n return translation;\n}\n"],"names":[],"mappings":";;;;;AAIO,MAAM,oBAAoB,MAAM,cAA0B,WAAW,EAAE;AACvE,MAAM,gBAAgB,MAAM,UAAsB,WAAW,EAAE;AAmB/D,MAAM,kBAAkB,CAAC,eAAwB;AACtD,QAAM,EAAE,SAAA,IAAa,kBAAA;AACrB,QAAM,CAAA,EAAG,WAAW,IAAI,WAAW,CAAC,MAAM,IAAI,GAAG,CAAC;AAGlD,QAAM,YAAY;AAAA,IAChB,CACE,KACA,YAIW;AACX,UAAI,CAAC,SAAU,SAAO,mCAAS,aAAY;AAC3C,aAAO,SAAS,EAAE,KAAK,EAAE,YAAY,QAAQ,mCAAS,QAAQ,UAAU,mCAAS,SAAA,CAAU;AAAA,IAC7F;AAAA,IACA,CAAC,UAAU,UAAU;AAAA,EAAA;AAGvB,YAAU,MAAM;AACd,QAAI,CAAC,SAAU;AAGf,UAAM,oBAAoB,SAAS,eAAe,MAAM;AACtD,kBAAA;AAAA,IACF,CAAC;AAGD,UAAM,oBAAoB,aACtB,SAAS,YAAY,UAAU,EAAE,gBAAgB,MAAM;AACrD,kBAAA;AAAA,IACF,CAAC,IACD,SAAS,gBAAgB,MAAM;AAC7B,kBAAA;AAAA,IACF,CAAC;AAEL,WAAO,MAAM;AACX,wBAAA;AACA,wBAAA;AAAA,IACF;AAAA,EACF,GAAG,CAAC,UAAU,UAAU,CAAC;AAEzB,SAAO;AAAA,IACL;AAAA,IACA,SAAQ,qCAAU,gBAAe;AAAA,EAAA;AAErC;AAGO,MAAM,iBAAiB,CAC5B,KACA,SAIA,eACG;AACH,QAAM,EAAE,UAAA,IAAc,gBAAgB,UAAU;AAChD,SAAO,UAAU,KAAK,OAAO;AAC/B;AAKO,MAAM,YAAY,MAAM;AAC7B,QAAM,EAAE,SAAA,IAAa,kBAAA;AACrB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiB,OAAM,qCAAU,gBAAe,IAAI;AAEhF,YAAU,MAAM;AACd,QAAI,CAAC,SAAU;AAEf,UAAM,cAAc,SAAS,eAAe,CAAC,EAAE,oBAAoB;AACjE,gBAAU,aAAa;AAAA,IACzB,CAAC;AAED,cAAU,SAAS,WAAW;AAE9B,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,CAAC;AAEb,SAAO;AACT;ACrEO,SAAS,UAAU,EAAE,GAAG,QAAQ,UAAU,YAAY,YAA4B;AACvF,QAAM,cAAc,eAAe,GAAG,EAAE,QAAQ,SAAA,GAAY,UAAU;AAEtE,MAAI,UAAU;AACZ,WAAO,SAAS,WAAW;AAAA,EAC7B;AAEA,SAAO;AACT;"}
@@ -1,2 +0,0 @@
1
- export { Fragment, useEffect, useRef, useState, useCallback, useMemo, useReducer } from 'react';
2
- export type { ReactNode, HTMLAttributes, CSSProperties } from 'react';
@@ -1 +0,0 @@
1
- export * from '@embedpdf/core/react';
@@ -1,2 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@embedpdf/core/react"),r=require("@embedpdf/plugin-i18n"),t=require("react"),a=()=>e.useCapability(r.I18nPlugin.id),n=e=>{const{provides:r}=a(),[,n]=t.useReducer(e=>e+1,0),o=t.useCallback((t,a)=>r?r.t(t,{documentId:e,params:null==a?void 0:a.params,fallback:null==a?void 0:a.fallback}):(null==a?void 0:a.fallback)??t,[r,e]);return t.useEffect(()=>{if(!r)return;const t=r.onLocaleChange(()=>{n()}),a=e?r.forDocument(e).onParamsChanged(()=>{n()}):r.onParamsChanged(()=>{n()});return()=>{t(),a()}},[r,e]),{translate:o,locale:(null==r?void 0:r.getLocale())??"en"}},o=(e,r,t)=>{const{translate:a}=n(t);return a(e,r)};exports.Translate=function({k:e,params:r,fallback:t,documentId:a,children:n}){const l=o(e,{params:r,fallback:t},a);return n?n(l):l},exports.useI18nCapability=a,exports.useI18nPlugin=()=>e.usePlugin(r.I18nPlugin.id),exports.useLocale=()=>{const{provides:e}=a(),[r,n]=t.useState(()=>(null==e?void 0:e.getLocale())??"en");return t.useEffect(()=>{if(!e)return;const r=e.onLocaleChange(({currentLocale:e})=>{n(e)});return n(e.getLocale()),r},[e]),r},exports.useTranslation=o,exports.useTranslations=n,Object.keys(r).forEach(e=>{"default"===e||Object.prototype.hasOwnProperty.call(exports,e)||Object.defineProperty(exports,e,{enumerable:!0,get:()=>r[e]})});
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","sources":["../../src/shared/hooks/use-i18n.ts","../../src/shared/components/translate.tsx"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/@framework';\nimport { I18nPlugin } from '@embedpdf/plugin-i18n';\nimport { useState, useEffect, useCallback, useReducer } from '@framework';\n\nexport const useI18nCapability = () => useCapability<I18nPlugin>(I18nPlugin.id);\nexport const useI18nPlugin = () => usePlugin<I18nPlugin>(I18nPlugin.id);\n\n/**\n * Hook to get a translate function for a component\n * Automatically updates all translations on locale or param changes\n *\n * @param documentId - Optional document ID for document-scoped translations\n * @returns translate function and current locale\n *\n * @example\n * const { translate } = useTranslations(documentId);\n * return (\n * <div>\n * <h1>{translate('page.title')}</h1>\n * <p>{translate('page.count', { params: { count: 5 } })}</p>\n * <p>{translate('unknown.key', { fallback: 'Default Text' })}</p>\n * </div>\n * );\n */\nexport const useTranslations = (documentId?: string) => {\n const { provides } = useI18nCapability();\n const [, forceUpdate] = useReducer((x) => x + 1, 0);\n\n // Create a stable translate function with options support\n const translate = useCallback(\n (\n key: string,\n options?: {\n params?: Record<string, string | number>;\n fallback?: string;\n },\n ): string => {\n if (!provides) return options?.fallback ?? key;\n return provides.t(key, { documentId, params: options?.params, fallback: options?.fallback });\n },\n [provides, documentId],\n );\n\n useEffect(() => {\n if (!provides) return;\n\n // Update on locale change (all translations need to refresh)\n const unsubscribeLocale = provides.onLocaleChange(() => {\n forceUpdate();\n });\n\n // Update on params change (for keys with param resolvers)\n const unsubscribeParams = documentId\n ? provides.forDocument(documentId).onParamsChanged(() => {\n forceUpdate(); // Re-render component when any params change\n })\n : provides.onParamsChanged(() => {\n forceUpdate(); // Global params change\n });\n\n return () => {\n unsubscribeLocale();\n unsubscribeParams();\n };\n }, [provides, documentId]);\n\n return {\n translate,\n locale: provides?.getLocale() ?? 'en',\n };\n};\n\n// Keep the old hook for single-key translations (useful for derived state)\nexport const useTranslation = (\n key: string,\n options?: {\n params?: Record<string, string | number>;\n fallback?: string;\n },\n documentId?: string,\n) => {\n const { translate } = useTranslations(documentId);\n return translate(key, options);\n};\n\n/**\n * Hook to get current locale\n */\nexport const useLocale = () => {\n const { provides } = useI18nCapability();\n const [locale, setLocale] = useState<string>(() => provides?.getLocale() ?? 'en');\n\n useEffect(() => {\n if (!provides) return;\n\n const unsubscribe = provides.onLocaleChange(({ currentLocale }) => {\n setLocale(currentLocale);\n });\n\n setLocale(provides.getLocale());\n\n return unsubscribe;\n }, [provides]);\n\n return locale;\n};\n","import { ReactNode } from '@framework';\nimport { useTranslation } from '../hooks';\n\ninterface TranslateProps {\n k: string; // translation key\n params?: Record<string, string | number>;\n fallback?: string; // Fallback string if translation not found\n documentId?: string; // Optional document ID for document-scoped translations\n children?: (translation: string) => ReactNode;\n}\n\n/**\n * Component for inline translation\n *\n * @example\n * // Global translation\n * <Translate k=\"loading.viewer\" />\n *\n * @example\n * // With explicit params\n * <Translate k=\"zoom.level\" params={{ level: 150 }} />\n *\n * @example\n * // With fallback\n * <Translate k=\"unknown.key\" fallback=\"Default Text\" />\n *\n * @example\n * // Document-scoped (uses param resolvers)\n * <Translate k=\"page.current\" documentId={documentId} />\n *\n * @example\n * // With render prop\n * <Translate k=\"zoom.level\" params={{ level: 150 }}>\n * {(text) => <span className=\"font-bold\">{text}</span>}\n * </Translate>\n */\nexport function Translate({ k, params, fallback, documentId, children }: TranslateProps) {\n const translation = useTranslation(k, { params, fallback }, documentId);\n\n if (children) {\n return children(translation);\n }\n\n return translation;\n}\n"],"names":["useI18nCapability","useCapability","I18nPlugin","id","useTranslations","documentId","provides","forceUpdate","useReducer","x","translate","useCallback","key","options","t","params","fallback","useEffect","unsubscribeLocale","onLocaleChange","unsubscribeParams","forDocument","onParamsChanged","locale","getLocale","useTranslation","k","children","translation","usePlugin","setLocale","useState","unsubscribe","currentLocale"],"mappings":"8KAIaA,EAAoB,IAAMC,gBAA0BC,EAAAA,WAAWC,IAoB/DC,EAAmBC,IAC9B,MAAMC,SAAEA,GAAaN,KACf,CAAGO,GAAeC,EAAAA,WAAYC,GAAMA,EAAI,EAAG,GAG3CC,EAAYC,EAAAA,YAChB,CACEC,EACAC,IAKKP,EACEA,EAASQ,EAAEF,EAAK,CAAEP,aAAYU,OAAQ,MAAAF,OAAA,EAAAA,EAASE,OAAQC,SAAU,MAAAH,OAAA,EAAAA,EAASG,YAD3D,MAAAH,OAAA,EAAAA,EAASG,WAAYJ,EAG7C,CAACN,EAAUD,IA0Bb,OAvBAY,EAAAA,UAAU,KACR,IAAKX,EAAU,OAGf,MAAMY,EAAoBZ,EAASa,eAAe,KAChDZ,MAIIa,EAAoBf,EACtBC,EAASe,YAAYhB,GAAYiB,gBAAgB,KAC/Cf,MAEFD,EAASgB,gBAAgB,KACvBf,MAGN,MAAO,KACLW,IACAE,MAED,CAACd,EAAUD,IAEP,CACLK,YACAa,cAAQjB,WAAUkB,cAAe,OAKxBC,EAAiB,CAC5Bb,EACAC,EAIAR,KAEA,MAAMK,UAAEA,GAAcN,EAAgBC,GACtC,OAAOK,EAAUE,EAAKC,sBC9CjB,UAAmBa,EAAEA,EAAAX,OAAGA,WAAQC,EAAAX,WAAUA,EAAAsB,SAAYA,IAC3D,MAAMC,EAAcH,EAAeC,EAAG,CAAEX,SAAQC,YAAYX,GAE5D,OAAIsB,EACKA,EAASC,GAGXA,CACT,oDDvC6B,IAAMC,YAAsB3B,EAAAA,WAAWC,sBAmF3C,KACvB,MAAMG,SAAEA,GAAaN,KACduB,EAAQO,GAAaC,EAAAA,SAAiB,KAAM,MAAAzB,OAAA,EAAAA,EAAUkB,cAAe,MAc5E,OAZAP,EAAAA,UAAU,KACR,IAAKX,EAAU,OAEf,MAAM0B,EAAc1B,EAASa,eAAe,EAAGc,oBAC7CH,EAAUG,KAKZ,OAFAH,EAAUxB,EAASkB,aAEZQ,GACN,CAAC1B,IAEGiB"}
@@ -1 +0,0 @@
1
- export * from '../shared-react';
@@ -1,69 +0,0 @@
1
- import { useCapability, usePlugin } from "@embedpdf/core/react";
2
- import { I18nPlugin } from "@embedpdf/plugin-i18n";
3
- export * from "@embedpdf/plugin-i18n";
4
- import { useState, useEffect, useReducer, useCallback } from "react";
5
- const useI18nCapability = () => useCapability(I18nPlugin.id);
6
- const useI18nPlugin = () => usePlugin(I18nPlugin.id);
7
- const useTranslations = (documentId) => {
8
- const { provides } = useI18nCapability();
9
- const [, forceUpdate] = useReducer((x) => x + 1, 0);
10
- const translate = useCallback(
11
- (key, options) => {
12
- if (!provides) return (options == null ? void 0 : options.fallback) ?? key;
13
- return provides.t(key, { documentId, params: options == null ? void 0 : options.params, fallback: options == null ? void 0 : options.fallback });
14
- },
15
- [provides, documentId]
16
- );
17
- useEffect(() => {
18
- if (!provides) return;
19
- const unsubscribeLocale = provides.onLocaleChange(() => {
20
- forceUpdate();
21
- });
22
- const unsubscribeParams = documentId ? provides.forDocument(documentId).onParamsChanged(() => {
23
- forceUpdate();
24
- }) : provides.onParamsChanged(() => {
25
- forceUpdate();
26
- });
27
- return () => {
28
- unsubscribeLocale();
29
- unsubscribeParams();
30
- };
31
- }, [provides, documentId]);
32
- return {
33
- translate,
34
- locale: (provides == null ? void 0 : provides.getLocale()) ?? "en"
35
- };
36
- };
37
- const useTranslation = (key, options, documentId) => {
38
- const { translate } = useTranslations(documentId);
39
- return translate(key, options);
40
- };
41
- const useLocale = () => {
42
- const { provides } = useI18nCapability();
43
- const [locale, setLocale] = useState(() => (provides == null ? void 0 : provides.getLocale()) ?? "en");
44
- useEffect(() => {
45
- if (!provides) return;
46
- const unsubscribe = provides.onLocaleChange(({ currentLocale }) => {
47
- setLocale(currentLocale);
48
- });
49
- setLocale(provides.getLocale());
50
- return unsubscribe;
51
- }, [provides]);
52
- return locale;
53
- };
54
- function Translate({ k, params, fallback, documentId, children }) {
55
- const translation = useTranslation(k, { params, fallback }, documentId);
56
- if (children) {
57
- return children(translation);
58
- }
59
- return translation;
60
- }
61
- export {
62
- Translate,
63
- useI18nCapability,
64
- useI18nPlugin,
65
- useLocale,
66
- useTranslation,
67
- useTranslations
68
- };
69
- //# sourceMappingURL=index.js.map