inline-i18n-multi 0.6.0 → 0.8.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.
package/README.md CHANGED
@@ -67,6 +67,12 @@ See "Hello" in your app? Just search for "Hello" in your codebase. **Done.**
67
67
  - **Interpolation Guards** - Handle missing variables gracefully (`missingVarHandler`)
68
68
  - **Locale Detection** - Auto-detect user locale from navigator, cookie, URL, or header (`detectLocale()`)
69
69
  - **Selectordinal** - Ordinal plural formatting (`{rank, selectordinal, one {#st} two {#nd} ...}`)
70
+ - **ICU Message Cache** - Memoize parsed ICU ASTs for performance (`icuCacheSize`, `clearICUCache()`)
71
+ - **Plural Shorthand** - Concise plural syntax (`{count, p, item|items}`)
72
+ - **Locale Persistence** - Auto-save/restore locale to cookie or localStorage (`persistLocale`, `restoreLocale()`)
73
+ - **Translation Scope** - Namespace scoping with `createScope` (`createScope('common')` returns a scoped `t()`)
74
+ - **Unused Key Detection** - CLI `--unused` flag to detect unused translation keys
75
+ - **TypeScript Type Generation** - `typegen` command for auto-generating translation key type definitions
70
76
 
71
77
  ---
72
78
 
@@ -551,6 +557,150 @@ Uses `Intl.PluralRules` with `{ type: 'ordinal' }` for locale-aware ordinal cate
551
557
 
552
558
  ---
553
559
 
560
+ ## ICU Message Cache
561
+
562
+ Memoize parsed ICU ASTs to avoid re-parsing the same message patterns:
563
+
564
+ ```typescript
565
+ import { configure, clearICUCache } from 'inline-i18n-multi'
566
+
567
+ // Enable caching (default: 500 entries)
568
+ configure({ icuCacheSize: 500 })
569
+
570
+ // Disable caching
571
+ configure({ icuCacheSize: 0 })
572
+
573
+ // Manually clear the cache
574
+ clearICUCache()
575
+ ```
576
+
577
+ The cache uses FIFO (First-In, First-Out) eviction when the maximum size is reached. Repeated calls to `it()` or `t()` with the same ICU pattern will reuse the cached AST instead of re-parsing.
578
+
579
+ ---
580
+
581
+ ## Plural Shorthand
582
+
583
+ Concise syntax for common plural patterns using `p` as a short type name:
584
+
585
+ ```typescript
586
+ import { it, setLocale } from 'inline-i18n-multi'
587
+
588
+ setLocale('en')
589
+
590
+ // 2-part: singular|plural (value is prepended automatically)
591
+ it({
592
+ en: '{count, p, item|items}',
593
+ ko: '{count, p, 개|개}'
594
+ }, { count: 1 }) // → "1 item"
595
+
596
+ it({
597
+ en: '{count, p, item|items}',
598
+ ko: '{count, p, 개|개}'
599
+ }, { count: 5 }) // → "5 items"
600
+
601
+ // 3-part: zero|singular|plural
602
+ it({
603
+ en: '{count, p, no items|item|items}',
604
+ ko: '{count, p, 항목 없음|개|개}'
605
+ }, { count: 0 }) // → "no items"
606
+
607
+ it({
608
+ en: '{count, p, no items|item|items}',
609
+ ko: '{count, p, 항목 없음|개|개}'
610
+ }, { count: 1 }) // → "1 item"
611
+
612
+ it({
613
+ en: '{count, p, no items|item|items}',
614
+ ko: '{count, p, 항목 없음|개|개}'
615
+ }, { count: 5 }) // → "5 items"
616
+ ```
617
+
618
+ The shorthand is preprocessed into standard ICU `plural` syntax before parsing.
619
+
620
+ ---
621
+
622
+ ## Locale Persistence
623
+
624
+ Auto-save and restore the user's locale to `cookie` or `localStorage`:
625
+
626
+ ```typescript
627
+ import { configure, setLocale, restoreLocale } from 'inline-i18n-multi'
628
+
629
+ // Configure persistence
630
+ configure({
631
+ persistLocale: {
632
+ storage: 'cookie', // 'cookie' | 'localStorage'
633
+ key: 'LOCALE', // storage key (default: 'LOCALE')
634
+ expires: 365 // cookie expiry in days (default: 365)
635
+ }
636
+ })
637
+
638
+ // Restore locale from storage (returns the saved locale or undefined)
639
+ const saved = restoreLocale()
640
+ if (saved) {
641
+ // locale was restored from storage
642
+ }
643
+
644
+ // setLocale() automatically saves to the configured storage
645
+ setLocale('ko') // also saves 'ko' to cookie or localStorage
646
+ ```
647
+
648
+ ```typescript
649
+ // localStorage example
650
+ configure({
651
+ persistLocale: {
652
+ storage: 'localStorage',
653
+ key: 'APP_LOCALE'
654
+ }
655
+ })
656
+
657
+ restoreLocale() // reads from localStorage
658
+ setLocale('ja') // saves 'ja' to localStorage
659
+ ```
660
+
661
+ ---
662
+
663
+ ## Translation Scope
664
+
665
+ Create a scoped translation function bound to a specific namespace with `createScope`:
666
+
667
+ ```typescript
668
+ import { createScope, loadDictionaries } from 'inline-i18n-multi'
669
+
670
+ loadDictionaries({ en: { greeting: 'Hello' }, ko: { greeting: '안녕하세요' } }, 'common')
671
+
672
+ const tc = createScope('common')
673
+ tc('greeting') // → "Hello"
674
+ ```
675
+
676
+ Eliminates the need to write namespace prefixes (`t('common:greeting')`) every time, keeping your code cleaner.
677
+
678
+ ---
679
+
680
+ ## Unused Key Detection
681
+
682
+ Use the `--unused` flag to detect translation keys defined in dictionaries but not used in your code:
683
+
684
+ ```bash
685
+ npx inline-i18n validate --unused
686
+ ```
687
+
688
+ Helps identify and clean up translations that are no longer needed.
689
+
690
+ ---
691
+
692
+ ## TypeScript Type Generation
693
+
694
+ Auto-generate TypeScript type definition files for your translation keys with the `typegen` command:
695
+
696
+ ```bash
697
+ npx inline-i18n typegen --output src/i18n.d.ts
698
+ ```
699
+
700
+ The generated types enable autocomplete and type checking for `t()` function key arguments.
701
+
702
+ ---
703
+
554
704
  ## Configuration
555
705
 
556
706
  Configure global settings for fallback behavior and warnings:
@@ -636,12 +786,15 @@ Available helpers:
636
786
 
637
787
  | Function | Description |
638
788
  |----------|-------------|
639
- | `configure(options)` | Configure global settings (fallback, warnings, debug, missingVarHandler) |
789
+ | `configure(options)` | Configure global settings (fallback, warnings, debug, missingVarHandler, icuCacheSize, persistLocale) |
640
790
  | `getConfig()` | Get current configuration |
641
791
  | `resetConfig()` | Reset configuration to defaults |
642
792
  | `loadAsync(locale, namespace?)` | Asynchronously load dictionary using configured loader |
643
793
  | `isLoaded(locale, namespace?)` | Check if dictionary has been loaded |
644
794
  | `parseRichText(template, names)` | Parse rich text template into segments |
795
+ | `clearICUCache()` | Clear the ICU message AST cache |
796
+ | `restoreLocale()` | Restore locale from configured persistent storage (cookie or localStorage) |
797
+ | `createScope(namespace)` | Return a translation function scoped to the given namespace |
645
798
 
646
799
  ### Custom Formatters
647
800
 
@@ -681,6 +834,17 @@ interface Config {
681
834
  debugMode?: boolean | DebugModeOptions
682
835
  loader?: (locale: Locale, namespace: string) => Promise<Record<string, unknown>>
683
836
  missingVarHandler?: (varName: string, locale: string) => string
837
+ icuCacheSize?: number
838
+ persistLocale?: PersistLocaleOptions
839
+ }
840
+
841
+ interface PersistLocaleOptions {
842
+ /** Storage backend */
843
+ storage: 'cookie' | 'localStorage'
844
+ /** Storage key (default: 'LOCALE') */
845
+ key?: string
846
+ /** Cookie expiry in days (default: 365, cookie only) */
847
+ expires?: number
684
848
  }
685
849
 
686
850
  interface DebugModeOptions {
package/dist/index.d.mts CHANGED
@@ -65,6 +65,17 @@ interface Config {
65
65
  loader?: (locale: Locale, namespace: string) => Promise<Record<string, unknown>>;
66
66
  /** Custom handler for missing interpolation variables (v0.6.0) */
67
67
  missingVarHandler?: (varName: string, locale: string) => string;
68
+ /** Maximum number of parsed ICU ASTs to cache (default: 500, 0 to disable) (v0.7.0) */
69
+ icuCacheSize?: number;
70
+ /** Locale persistence configuration (v0.7.0) */
71
+ persistLocale?: {
72
+ /** Storage mechanism */
73
+ storage: 'cookie' | 'localStorage';
74
+ /** Storage key name (default: 'LOCALE') */
75
+ key?: string;
76
+ /** Cookie expiry in days (default: 365). Only applies to cookie storage. */
77
+ expires?: number;
78
+ };
68
79
  }
69
80
 
70
81
  /**
@@ -83,6 +94,11 @@ declare function it(translations: Translations, vars?: TranslationVars): string;
83
94
 
84
95
  declare function setLocale(locale: Locale): void;
85
96
  declare function getLocale(): Locale;
97
+ /**
98
+ * Restore locale from configured persistent storage.
99
+ * Returns the restored locale, or undefined if nothing was found.
100
+ */
101
+ declare function restoreLocale(): Locale | undefined;
86
102
 
87
103
  /**
88
104
  * Runtime lookup function for build-tool-transformed code.
@@ -212,7 +228,7 @@ declare function getDictionary(locale: Locale, namespace?: string): Dictionary |
212
228
  */
213
229
  declare function getLoadedNamespaces(): string[];
214
230
 
215
- type FullConfig = Required<Omit<Config, 'loader' | 'missingVarHandler'>> & Pick<Config, 'loader' | 'missingVarHandler'>;
231
+ type FullConfig = Required<Omit<Config, 'loader' | 'missingVarHandler' | 'persistLocale'>> & Pick<Config, 'loader' | 'missingVarHandler' | 'persistLocale'>;
216
232
  /**
217
233
  * Configure inline-i18n-multi settings
218
234
  *
@@ -233,6 +249,10 @@ declare function getConfig(): FullConfig;
233
249
  */
234
250
  declare function resetConfig(): void;
235
251
 
252
+ /**
253
+ * Clear the ICU message AST cache
254
+ */
255
+ declare function clearICUCache(): void;
236
256
  type CustomFormatter = (value: unknown, locale: string, style?: string) => string;
237
257
  /**
238
258
  * Register a custom formatter
@@ -275,6 +295,21 @@ interface DetectLocaleOptions {
275
295
  */
276
296
  declare function detectLocale(options: DetectLocaleOptions): Locale;
277
297
 
298
+ /**
299
+ * Create a scoped translation function for a given namespace.
300
+ * Returns a `t()` wrapper that auto-prepends the namespace prefix.
301
+ *
302
+ * @param namespace - Namespace to scope to
303
+ * @returns Scoped translation function
304
+ *
305
+ * @example
306
+ * const tc = createScope('common')
307
+ * tc('greeting') // equivalent to t('common:greeting')
308
+ * tc('nav.home') // equivalent to t('common:nav.home')
309
+ * tc('welcome', { name: 'John' })
310
+ */
311
+ declare function createScope(namespace: string): (key: string, vars?: TranslationVars, locale?: Locale) => string;
312
+
278
313
  /**
279
314
  * Rich Text segment types
280
315
  */
@@ -303,4 +338,4 @@ interface RichTextSegment {
303
338
  */
304
339
  declare function parseRichText(template: string, componentNames: string[]): RichTextSegment[];
305
340
 
306
- export { type Config, type CustomFormatter, type DebugModeOptions, type DetectLocaleOptions, type DetectSource, type Dictionaries, type Dictionary, type Locale, type PluralRules, type RichTextSegment, type TranslationVars, type TranslationWarning, type Translations, type WarningHandler, __i18n_lookup, clearDictionaries, clearFormatters, configure, detectLocale, en_de, en_es, en_fr, en_ja, en_zh, getConfig, getDictionary, getLoadedLocales, getLoadedNamespaces, getLocale, hasTranslation, isLoaded, it, it_de, it_es, it_fr, it_ja, it_zh, ja_es, ja_zh, loadAsync, loadDictionaries, loadDictionary, parseRichText, registerFormatter, resetConfig, setLocale, t, zh_es };
341
+ export { type Config, type CustomFormatter, type DebugModeOptions, type DetectLocaleOptions, type DetectSource, type Dictionaries, type Dictionary, type Locale, type PluralRules, type RichTextSegment, type TranslationVars, type TranslationWarning, type Translations, type WarningHandler, __i18n_lookup, clearDictionaries, clearFormatters, clearICUCache, configure, createScope, detectLocale, en_de, en_es, en_fr, en_ja, en_zh, getConfig, getDictionary, getLoadedLocales, getLoadedNamespaces, getLocale, hasTranslation, isLoaded, it, it_de, it_es, it_fr, it_ja, it_zh, ja_es, ja_zh, loadAsync, loadDictionaries, loadDictionary, parseRichText, registerFormatter, resetConfig, restoreLocale, setLocale, t, zh_es };
package/dist/index.d.ts CHANGED
@@ -65,6 +65,17 @@ interface Config {
65
65
  loader?: (locale: Locale, namespace: string) => Promise<Record<string, unknown>>;
66
66
  /** Custom handler for missing interpolation variables (v0.6.0) */
67
67
  missingVarHandler?: (varName: string, locale: string) => string;
68
+ /** Maximum number of parsed ICU ASTs to cache (default: 500, 0 to disable) (v0.7.0) */
69
+ icuCacheSize?: number;
70
+ /** Locale persistence configuration (v0.7.0) */
71
+ persistLocale?: {
72
+ /** Storage mechanism */
73
+ storage: 'cookie' | 'localStorage';
74
+ /** Storage key name (default: 'LOCALE') */
75
+ key?: string;
76
+ /** Cookie expiry in days (default: 365). Only applies to cookie storage. */
77
+ expires?: number;
78
+ };
68
79
  }
69
80
 
70
81
  /**
@@ -83,6 +94,11 @@ declare function it(translations: Translations, vars?: TranslationVars): string;
83
94
 
84
95
  declare function setLocale(locale: Locale): void;
85
96
  declare function getLocale(): Locale;
97
+ /**
98
+ * Restore locale from configured persistent storage.
99
+ * Returns the restored locale, or undefined if nothing was found.
100
+ */
101
+ declare function restoreLocale(): Locale | undefined;
86
102
 
87
103
  /**
88
104
  * Runtime lookup function for build-tool-transformed code.
@@ -212,7 +228,7 @@ declare function getDictionary(locale: Locale, namespace?: string): Dictionary |
212
228
  */
213
229
  declare function getLoadedNamespaces(): string[];
214
230
 
215
- type FullConfig = Required<Omit<Config, 'loader' | 'missingVarHandler'>> & Pick<Config, 'loader' | 'missingVarHandler'>;
231
+ type FullConfig = Required<Omit<Config, 'loader' | 'missingVarHandler' | 'persistLocale'>> & Pick<Config, 'loader' | 'missingVarHandler' | 'persistLocale'>;
216
232
  /**
217
233
  * Configure inline-i18n-multi settings
218
234
  *
@@ -233,6 +249,10 @@ declare function getConfig(): FullConfig;
233
249
  */
234
250
  declare function resetConfig(): void;
235
251
 
252
+ /**
253
+ * Clear the ICU message AST cache
254
+ */
255
+ declare function clearICUCache(): void;
236
256
  type CustomFormatter = (value: unknown, locale: string, style?: string) => string;
237
257
  /**
238
258
  * Register a custom formatter
@@ -275,6 +295,21 @@ interface DetectLocaleOptions {
275
295
  */
276
296
  declare function detectLocale(options: DetectLocaleOptions): Locale;
277
297
 
298
+ /**
299
+ * Create a scoped translation function for a given namespace.
300
+ * Returns a `t()` wrapper that auto-prepends the namespace prefix.
301
+ *
302
+ * @param namespace - Namespace to scope to
303
+ * @returns Scoped translation function
304
+ *
305
+ * @example
306
+ * const tc = createScope('common')
307
+ * tc('greeting') // equivalent to t('common:greeting')
308
+ * tc('nav.home') // equivalent to t('common:nav.home')
309
+ * tc('welcome', { name: 'John' })
310
+ */
311
+ declare function createScope(namespace: string): (key: string, vars?: TranslationVars, locale?: Locale) => string;
312
+
278
313
  /**
279
314
  * Rich Text segment types
280
315
  */
@@ -303,4 +338,4 @@ interface RichTextSegment {
303
338
  */
304
339
  declare function parseRichText(template: string, componentNames: string[]): RichTextSegment[];
305
340
 
306
- export { type Config, type CustomFormatter, type DebugModeOptions, type DetectLocaleOptions, type DetectSource, type Dictionaries, type Dictionary, type Locale, type PluralRules, type RichTextSegment, type TranslationVars, type TranslationWarning, type Translations, type WarningHandler, __i18n_lookup, clearDictionaries, clearFormatters, configure, detectLocale, en_de, en_es, en_fr, en_ja, en_zh, getConfig, getDictionary, getLoadedLocales, getLoadedNamespaces, getLocale, hasTranslation, isLoaded, it, it_de, it_es, it_fr, it_ja, it_zh, ja_es, ja_zh, loadAsync, loadDictionaries, loadDictionary, parseRichText, registerFormatter, resetConfig, setLocale, t, zh_es };
341
+ export { type Config, type CustomFormatter, type DebugModeOptions, type DetectLocaleOptions, type DetectSource, type Dictionaries, type Dictionary, type Locale, type PluralRules, type RichTextSegment, type TranslationVars, type TranslationWarning, type Translations, type WarningHandler, __i18n_lookup, clearDictionaries, clearFormatters, clearICUCache, configure, createScope, detectLocale, en_de, en_es, en_fr, en_ja, en_zh, getConfig, getDictionary, getLoadedLocales, getLoadedNamespaces, getLocale, hasTranslation, isLoaded, it, it_de, it_es, it_fr, it_ja, it_zh, ja_es, ja_zh, loadAsync, loadDictionaries, loadDictionary, parseRichText, registerFormatter, resetConfig, restoreLocale, setLocale, t, zh_es };
package/dist/index.js CHANGED
@@ -2,15 +2,6 @@
2
2
 
3
3
  var icuMessageformatParser = require('@formatjs/icu-messageformat-parser');
4
4
 
5
- // src/context.ts
6
- var currentLocale = "en";
7
- function setLocale(locale) {
8
- currentLocale = locale;
9
- }
10
- function getLocale() {
11
- return currentLocale;
12
- }
13
-
14
5
  // src/config.ts
15
6
  function defaultWarningHandler(warning) {
16
7
  const parts = [`[inline-i18n] Missing translation for locale "${warning.requestedLocale}"`];
@@ -42,7 +33,8 @@ var defaultConfig = {
42
33
  warnOnMissing: isDevMode(),
43
34
  onMissingTranslation: defaultWarningHandler,
44
35
  debugMode: false,
45
- loader: void 0
36
+ loader: void 0,
37
+ icuCacheSize: 500
46
38
  };
47
39
  var config = { ...defaultConfig };
48
40
  function configure(options) {
@@ -110,6 +102,82 @@ function applyDebugFormat(output, debugInfo) {
110
102
  }
111
103
  return output;
112
104
  }
105
+
106
+ // src/context.ts
107
+ var currentLocale = "en";
108
+ function setCookie(name, value, days) {
109
+ if (typeof document === "undefined") return;
110
+ const expires = new Date(Date.now() + days * 864e5).toUTCString();
111
+ document.cookie = `${name}=${value}; expires=${expires}; path=/; SameSite=Lax`;
112
+ }
113
+ function getCookie(name) {
114
+ if (typeof document === "undefined") return void 0;
115
+ const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
116
+ return match?.[1];
117
+ }
118
+ function persistLocaleToStorage(locale) {
119
+ const cfg = getConfig();
120
+ if (!cfg.persistLocale) return;
121
+ const { storage, key = "LOCALE", expires = 365 } = cfg.persistLocale;
122
+ if (storage === "cookie") {
123
+ setCookie(key, locale, expires);
124
+ } else if (storage === "localStorage") {
125
+ if (typeof localStorage !== "undefined") {
126
+ try {
127
+ localStorage.setItem(key, locale);
128
+ } catch {
129
+ }
130
+ }
131
+ }
132
+ }
133
+ function setLocale(locale) {
134
+ currentLocale = locale;
135
+ persistLocaleToStorage(locale);
136
+ }
137
+ function getLocale() {
138
+ return currentLocale;
139
+ }
140
+ function restoreLocale() {
141
+ const cfg = getConfig();
142
+ if (!cfg.persistLocale) return void 0;
143
+ const { storage, key = "LOCALE" } = cfg.persistLocale;
144
+ let stored;
145
+ if (storage === "cookie") {
146
+ stored = getCookie(key);
147
+ } else if (storage === "localStorage") {
148
+ if (typeof localStorage !== "undefined") {
149
+ try {
150
+ stored = localStorage.getItem(key) ?? void 0;
151
+ } catch {
152
+ }
153
+ }
154
+ }
155
+ if (stored) {
156
+ currentLocale = stored;
157
+ return stored;
158
+ }
159
+ return void 0;
160
+ }
161
+ var icuCache = /* @__PURE__ */ new Map();
162
+ function clearICUCache() {
163
+ icuCache.clear();
164
+ }
165
+ function cachedParse(template) {
166
+ const cached = icuCache.get(template);
167
+ if (cached) return cached;
168
+ const ast = icuMessageformatParser.parse(template);
169
+ const cfg = getConfig();
170
+ if (cfg.icuCacheSize > 0) {
171
+ if (icuCache.size >= cfg.icuCacheSize) {
172
+ const firstKey = icuCache.keys().next().value;
173
+ if (firstKey !== void 0) {
174
+ icuCache.delete(firstKey);
175
+ }
176
+ }
177
+ icuCache.set(template, ast);
178
+ }
179
+ return ast;
180
+ }
113
181
  function handleMissingVar(varName, locale) {
114
182
  const cfg = getConfig();
115
183
  if (cfg.missingVarHandler) {
@@ -404,13 +472,34 @@ function formatListValue(variableName, type, style, vars, locale) {
404
472
  return value.join(", ");
405
473
  }
406
474
  }
475
+ var PLURAL_SHORTHAND_PATTERN = /\{(\w+),\s*p,\s*([^}]+)\}/g;
476
+ function hasPluralShorthand(template) {
477
+ PLURAL_SHORTHAND_PATTERN.lastIndex = 0;
478
+ return PLURAL_SHORTHAND_PATTERN.test(template);
479
+ }
480
+ function preprocessPluralShorthand(template) {
481
+ PLURAL_SHORTHAND_PATTERN.lastIndex = 0;
482
+ return template.replace(PLURAL_SHORTHAND_PATTERN, (_, variable, args) => {
483
+ const parts = args.split("|").map((s) => s.trim());
484
+ if (parts.length === 2) {
485
+ const [singular, plural] = parts;
486
+ return `{${variable}, plural, one {# ${singular}} other {# ${plural}}}`;
487
+ }
488
+ if (parts.length === 3) {
489
+ const [zero, singular, plural] = parts;
490
+ return `{${variable}, plural, =0 {${zero}} one {# ${singular}} other {# ${plural}}}`;
491
+ }
492
+ return `{${variable}, p, ${args}}`;
493
+ });
494
+ }
407
495
  function interpolateICU(template, vars, locale) {
408
- const { processed: afterCustom, replacements: customReplacements } = preprocessCustomFormatters(template);
496
+ const afterPluralShorthand = preprocessPluralShorthand(template);
497
+ const { processed: afterCustom, replacements: customReplacements } = preprocessCustomFormatters(afterPluralShorthand);
409
498
  const { processed: afterCurrency, replacements: currencyReplacements } = preprocessCurrency(afterCustom);
410
499
  const { processed: afterCompact, replacements: compactReplacements } = preprocessCompactNumber(afterCurrency);
411
500
  const { processed: afterRelTime, replacements: relTimeReplacements } = preprocessRelativeTime(afterCompact);
412
501
  const { processed: afterList, replacements: listReplacements } = preprocessList(afterRelTime);
413
- const ast = icuMessageformatParser.parse(afterList);
502
+ const ast = cachedParse(afterList);
414
503
  let result = formatElements(ast, vars, locale, null);
415
504
  for (const [placeholder, { variable, formatterName, style }] of customReplacements) {
416
505
  const value = vars[variable];
@@ -507,7 +596,7 @@ function formatSelect(el, vars, locale) {
507
596
  }
508
597
  return handleMissingVar(el.value, locale);
509
598
  }
510
- var ICU_PATTERN = /\{[^}]+,\s*(plural|select|selectordinal|number|date|time|relativeTime|list|currency)\s*[,}]/;
599
+ var ICU_PATTERN = /\{[^}]+,\s*(plural|select|selectordinal|number|date|time|relativeTime|list|currency|p)\s*[,}]/;
511
600
  function hasICUPattern(template) {
512
601
  return ICU_PATTERN.test(template);
513
602
  }
@@ -516,7 +605,7 @@ function hasICUPattern(template) {
516
605
  var VARIABLE_PATTERN = /\{(\w+)\}/g;
517
606
  function interpolate(template, vars, locale) {
518
607
  const resolvedLocale = locale || "en";
519
- if (hasICUPattern(template) || hasCustomFormatter(template)) {
608
+ if (hasICUPattern(template) || hasCustomFormatter(template) || hasPluralShorthand(template)) {
520
609
  if (!vars) {
521
610
  const cfg = getConfig();
522
611
  if (cfg.missingVarHandler) {
@@ -961,6 +1050,13 @@ function detectLocale(options) {
961
1050
  return defaultLocale;
962
1051
  }
963
1052
 
1053
+ // src/scope.ts
1054
+ function createScope(namespace) {
1055
+ return (key, vars, locale) => {
1056
+ return t(`${namespace}:${key}`, vars, locale);
1057
+ };
1058
+ }
1059
+
964
1060
  // src/richtext.ts
965
1061
  function escapeRegExp(str) {
966
1062
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -1000,7 +1096,9 @@ function parseRichText(template, componentNames) {
1000
1096
  exports.__i18n_lookup = __i18n_lookup;
1001
1097
  exports.clearDictionaries = clearDictionaries;
1002
1098
  exports.clearFormatters = clearFormatters;
1099
+ exports.clearICUCache = clearICUCache;
1003
1100
  exports.configure = configure;
1101
+ exports.createScope = createScope;
1004
1102
  exports.detectLocale = detectLocale;
1005
1103
  exports.en_de = en_de;
1006
1104
  exports.en_es = en_es;
@@ -1028,6 +1126,7 @@ exports.loadDictionary = loadDictionary;
1028
1126
  exports.parseRichText = parseRichText;
1029
1127
  exports.registerFormatter = registerFormatter;
1030
1128
  exports.resetConfig = resetConfig;
1129
+ exports.restoreLocale = restoreLocale;
1031
1130
  exports.setLocale = setLocale;
1032
1131
  exports.t = t;
1033
1132
  exports.zh_es = zh_es;