inline-i18n-multi 0.4.0 → 0.6.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
@@ -59,6 +59,14 @@ See "Hello" in your app? Just search for "Hello" in your codebase. **Done.**
59
59
  - **Missing Translation Warning** - Development-time diagnostics with customizable handlers
60
60
  - **Namespace Support** - Organize translations for large apps (`t('common:greeting')`)
61
61
  - **Debug Mode** - Visual indicators for missing/fallback translations
62
+ - **Currency Formatting** - Locale-aware currency display (`{price, currency, USD}`)
63
+ - **Compact Number Formatting** - Short number display (`{count, number, compact}`)
64
+ - **Rich Text Interpolation** - Embed React components in translations (`<link>text</link>`)
65
+ - **Lazy Loading** - Async dictionary loading on demand (`loadAsync()`)
66
+ - **Custom Formatter Registry** - Register custom ICU-style formatters (`registerFormatter('phone', fn)`)
67
+ - **Interpolation Guards** - Handle missing variables gracefully (`missingVarHandler`)
68
+ - **Locale Detection** - Auto-detect user locale from navigator, cookie, URL, or header (`detectLocale()`)
69
+ - **Selectordinal** - Ordinal plural formatting (`{rank, selectordinal, one {#st} two {#nd} ...}`)
62
70
 
63
71
  ---
64
72
 
@@ -202,8 +210,11 @@ it({
202
210
  }, { price: 1234.56 }) // → "Price: 1,234.56"
203
211
  ```
204
212
 
205
- **Supported ICU styles:**
206
- - `number`: `decimal`, `percent`, `integer`, `currency`
213
+ **Supported ICU types:**
214
+ - `plural`: `zero`, `one`, `two`, `few`, `many`, `other` (and exact matches like `=0`, `=1`)
215
+ - `select`: match on string values
216
+ - `selectordinal`: ordinal plural categories (`one`, `two`, `few`, `other`)
217
+ - `number`: `decimal`, `percent`, `integer`, `currency`, `compact`, `compactLong`
207
218
  - `date`: `short`, `medium`, `long`, `full`
208
219
  - `time`: `short`, `medium`, `long`, `full`
209
220
 
@@ -234,6 +245,33 @@ it({ en: '{options, list, disjunction}' }, { options: ['A', 'B'] })
234
245
  // → "A or B"
235
246
  ```
236
247
 
248
+ ### Currency Formatting
249
+
250
+ ```typescript
251
+ it({
252
+ en: 'Total: {price, currency, USD}',
253
+ ko: '합계: {price, currency, KRW}'
254
+ }, { price: 42000 })
255
+ // en → "Total: $42,000.00" / ko → "합계: ₩42,000"
256
+
257
+ // Defaults to USD when currency code omitted
258
+ it({ en: '{price, currency}' }, { price: 100 })
259
+ // → "$100.00"
260
+ ```
261
+
262
+ ### Compact Number Formatting
263
+
264
+ ```typescript
265
+ it({
266
+ en: '{count, number, compact} views',
267
+ ko: '{count, number, compact} 조회'
268
+ }, { count: 1500000 })
269
+ // en → "1.5M views" / ko → "150만 조회"
270
+
271
+ it({ en: '{count, number, compactLong}' }, { count: 1500000 })
272
+ // → "1.5 million"
273
+ ```
274
+
237
275
  ---
238
276
 
239
277
  ## Namespace Support
@@ -278,6 +316,241 @@ t('missing.key') // → "[MISSING: fr] missing.key"
278
316
 
279
317
  ---
280
318
 
319
+ ## Rich Text Interpolation
320
+
321
+ Embed React components within translations:
322
+
323
+ ```tsx
324
+ import { RichText, useRichText } from 'inline-i18n-multi-react'
325
+
326
+ // Component syntax
327
+ <RichText
328
+ translations={{
329
+ en: 'Read <link>terms</link> and <bold>agree</bold>',
330
+ ko: '<link>약관</link>을 읽고 <bold>동의</bold>해주세요'
331
+ }}
332
+ components={{
333
+ link: (text) => <a href="/terms">{text}</a>,
334
+ bold: (text) => <strong>{text}</strong>
335
+ }}
336
+ />
337
+
338
+ // Hook syntax
339
+ const richT = useRichText({
340
+ link: (text) => <a href="/terms">{text}</a>,
341
+ bold: (text) => <strong>{text}</strong>
342
+ })
343
+ richT({ en: 'Click <link>here</link>', ko: '<link>여기</link> 클릭' })
344
+ ```
345
+
346
+ ---
347
+
348
+ ## Lazy Loading
349
+
350
+ Load dictionaries asynchronously on demand:
351
+
352
+ ```typescript
353
+ import { configure, loadAsync, isLoaded, t } from 'inline-i18n-multi'
354
+
355
+ configure({
356
+ loader: (locale, namespace) => import(`./locales/${locale}/${namespace}.json`)
357
+ })
358
+
359
+ await loadAsync('ko', 'dashboard')
360
+ t('dashboard:title')
361
+
362
+ isLoaded('ko', 'dashboard') // → true
363
+ ```
364
+
365
+ ### React Hook
366
+
367
+ ```tsx
368
+ import { useLoadDictionaries } from 'inline-i18n-multi-react'
369
+
370
+ function Dashboard() {
371
+ const { isLoading, error } = useLoadDictionaries('ko', 'dashboard')
372
+ if (isLoading) return <Spinner />
373
+ if (error) return <Error message={error.message} />
374
+ return <Content />
375
+ }
376
+ ```
377
+
378
+ ---
379
+
380
+ ## Custom Formatter Registry
381
+
382
+ Register custom ICU-style formatters for domain-specific formatting:
383
+
384
+ ```typescript
385
+ import { registerFormatter, clearFormatters, it, setLocale } from 'inline-i18n-multi'
386
+
387
+ setLocale('en')
388
+
389
+ // Register a phone number formatter
390
+ registerFormatter('phone', (value, locale, style?) => {
391
+ const s = String(value)
392
+ if (locale === 'ko') return `${s.slice(0, 3)}-${s.slice(3, 7)}-${s.slice(7)}`
393
+ return `(${s.slice(0, 3)}) ${s.slice(3, 6)}-${s.slice(6)}`
394
+ })
395
+
396
+ // Use in translations
397
+ it({
398
+ en: 'Call {num, phone}',
399
+ ko: '전화: {num, phone}'
400
+ }, { num: '2125551234' })
401
+ // → "Call (212) 555-1234"
402
+
403
+ // Register a formatter with style support
404
+ registerFormatter('mask', (value, locale, style?) => {
405
+ const s = String(value)
406
+ if (style === 'email') {
407
+ const [user, domain] = s.split('@')
408
+ return `${user[0]}***@${domain}`
409
+ }
410
+ return s.slice(0, 2) + '***' + s.slice(-2)
411
+ })
412
+
413
+ it({ en: 'Email: {email, mask, email}' }, { email: 'john@example.com' })
414
+ // → "Email: j***@example.com"
415
+
416
+ // Clear all custom formatters
417
+ clearFormatters()
418
+ ```
419
+
420
+ Reserved names (`plural`, `select`, `selectordinal`, `number`, `date`, `time`, `relativeTime`, `list`, `currency`) cannot be used as custom formatter names and will throw an error.
421
+
422
+ ---
423
+
424
+ ## Interpolation Guards
425
+
426
+ Handle missing interpolation variables gracefully with a custom handler:
427
+
428
+ ```typescript
429
+ import { configure, it, setLocale } from 'inline-i18n-multi'
430
+
431
+ setLocale('en')
432
+
433
+ // Configure a missing variable handler
434
+ configure({
435
+ missingVarHandler: (varName, locale) => {
436
+ console.warn(`Missing variable "${varName}" for locale "${locale}"`)
437
+ return `[${varName}]`
438
+ }
439
+ })
440
+
441
+ // When a variable is missing, the handler is called instead of leaving {varName}
442
+ it({ en: 'Hello, {name}!' })
443
+ // logs: Missing variable "name" for locale "en"
444
+ // → "Hello, [name]!"
445
+
446
+ // Works with ICU patterns too
447
+ it({ en: '{count, plural, one {# item} other {# items}}' })
448
+ // logs: Missing variable "count" for locale "en"
449
+ // → "{count}"
450
+
451
+ // Without a handler, missing variables are left as-is: {varName}
452
+ ```
453
+
454
+ ---
455
+
456
+ ## Locale Detection
457
+
458
+ Auto-detect the user's locale from multiple sources:
459
+
460
+ ```typescript
461
+ import { detectLocale, setLocale } from 'inline-i18n-multi'
462
+
463
+ // Basic detection from browser navigator
464
+ const locale = detectLocale({
465
+ supportedLocales: ['en', 'ko', 'ja'],
466
+ defaultLocale: 'en',
467
+ })
468
+ setLocale(locale)
469
+
470
+ // Multiple sources in priority order
471
+ const detected = detectLocale({
472
+ supportedLocales: ['en', 'ko', 'ja'],
473
+ defaultLocale: 'en',
474
+ sources: ['cookie', 'url', 'navigator'],
475
+ cookieName: 'NEXT_LOCALE', // default
476
+ })
477
+
478
+ // Server-side detection from Accept-Language header
479
+ const ssrLocale = detectLocale({
480
+ supportedLocales: ['en', 'ko', 'ja'],
481
+ defaultLocale: 'en',
482
+ sources: ['header'],
483
+ headerValue: 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
484
+ })
485
+ // → "ko"
486
+
487
+ // BCP 47 parent matching (en-US matches en)
488
+ const matched = detectLocale({
489
+ supportedLocales: ['en', 'ko'],
490
+ defaultLocale: 'en',
491
+ sources: ['navigator'],
492
+ })
493
+ // Browser reports "en-US" → matches "en"
494
+ ```
495
+
496
+ **Detection sources:**
497
+
498
+ | Source | Description |
499
+ |--------|-------------|
500
+ | `navigator` | Browser `navigator.languages` / `navigator.language` |
501
+ | `cookie` | Reads locale from `document.cookie` (configurable name) |
502
+ | `url` | First path segment (e.g., `/ko/about` matches `ko`) |
503
+ | `header` | Parses `Accept-Language` header value (for SSR) |
504
+
505
+ Sources are tried in order; the first match wins. If no source matches, `defaultLocale` is returned.
506
+
507
+ ---
508
+
509
+ ## Selectordinal
510
+
511
+ Ordinal plural formatting for ranking and ordering (e.g., 1st, 2nd, 3rd):
512
+
513
+ ```typescript
514
+ import { it, setLocale } from 'inline-i18n-multi'
515
+
516
+ setLocale('en')
517
+
518
+ // Ordinal suffixes
519
+ it({
520
+ en: '{rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}'
521
+ }, { rank: 1 }) // → "1st"
522
+
523
+ it({
524
+ en: '{rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}'
525
+ }, { rank: 2 }) // → "2nd"
526
+
527
+ it({
528
+ en: '{rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}'
529
+ }, { rank: 3 }) // → "3rd"
530
+
531
+ it({
532
+ en: '{rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}'
533
+ }, { rank: 4 }) // → "4th"
534
+
535
+ // Handles English irregulars correctly
536
+ it({
537
+ en: '{rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}'
538
+ }, { rank: 11 }) // → "11th" (not "11st")
539
+
540
+ it({
541
+ en: '{rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}}'
542
+ }, { rank: 21 }) // → "21st"
543
+
544
+ // Combined with text
545
+ it({
546
+ en: 'You finished {rank, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} place!'
547
+ }, { rank: 3 }) // → "You finished 3rd place!"
548
+ ```
549
+
550
+ Uses `Intl.PluralRules` with `{ type: 'ordinal' }` for locale-aware ordinal categories.
551
+
552
+ ---
553
+
281
554
  ## Configuration
282
555
 
283
556
  Configure global settings for fallback behavior and warnings:
@@ -363,9 +636,33 @@ Available helpers:
363
636
 
364
637
  | Function | Description |
365
638
  |----------|-------------|
366
- | `configure(options)` | Configure global settings (fallback, warnings, debug) |
639
+ | `configure(options)` | Configure global settings (fallback, warnings, debug, missingVarHandler) |
367
640
  | `getConfig()` | Get current configuration |
368
641
  | `resetConfig()` | Reset configuration to defaults |
642
+ | `loadAsync(locale, namespace?)` | Asynchronously load dictionary using configured loader |
643
+ | `isLoaded(locale, namespace?)` | Check if dictionary has been loaded |
644
+ | `parseRichText(template, names)` | Parse rich text template into segments |
645
+
646
+ ### Custom Formatters
647
+
648
+ | Function | Description |
649
+ |----------|-------------|
650
+ | `registerFormatter(name, formatter)` | Register a custom ICU-style formatter |
651
+ | `clearFormatters()` | Clear all custom formatters |
652
+
653
+ ### Locale Detection
654
+
655
+ | Function | Description |
656
+ |----------|-------------|
657
+ | `detectLocale(options)` | Auto-detect user's locale from multiple sources |
658
+
659
+ ### React Hooks & Components
660
+
661
+ | Export | Description |
662
+ |--------|-------------|
663
+ | `RichText` | Rich text translation component with embedded components |
664
+ | `useRichText(components)` | Hook returning function for rich text translations |
665
+ | `useLoadDictionaries(locale, ns?)` | Hook for lazy loading dictionaries with loading state |
369
666
 
370
667
  ### Types
371
668
 
@@ -382,6 +679,8 @@ interface Config {
382
679
  warnOnMissing?: boolean
383
680
  onMissingTranslation?: WarningHandler
384
681
  debugMode?: boolean | DebugModeOptions
682
+ loader?: (locale: Locale, namespace: string) => Promise<Record<string, unknown>>
683
+ missingVarHandler?: (varName: string, locale: string) => string
385
684
  }
386
685
 
387
686
  interface DebugModeOptions {
@@ -400,6 +699,29 @@ interface TranslationWarning {
400
699
  }
401
700
 
402
701
  type WarningHandler = (warning: TranslationWarning) => void
702
+
703
+ type CustomFormatter = (value: unknown, locale: string, style?: string) => string
704
+
705
+ type DetectSource = 'navigator' | 'cookie' | 'url' | 'header'
706
+
707
+ interface DetectLocaleOptions {
708
+ /** Locales your app supports */
709
+ supportedLocales: Locale[]
710
+ /** Fallback when no source matches */
711
+ defaultLocale: Locale
712
+ /** Detection sources in priority order (default: ['navigator']) */
713
+ sources?: DetectSource[]
714
+ /** Cookie name to read (default: 'NEXT_LOCALE') */
715
+ cookieName?: string
716
+ /** Accept-Language header value (for SSR) */
717
+ headerValue?: string
718
+ }
719
+
720
+ interface RichTextSegment {
721
+ type: 'text' | 'component'
722
+ content: string
723
+ componentName?: string
724
+ }
403
725
  ```
404
726
 
405
727
  ---
@@ -454,30 +776,6 @@ npm install inline-i18n-multi-next
454
776
 
455
777
  ---
456
778
 
457
- ## Build-Time Optimization
458
-
459
- ### Babel Plugin
460
-
461
- Transform `it()` calls at build time for better performance. Extracts translations for static analysis and enables dead code elimination for unused locales.
462
-
463
- ```bash
464
- npm install -D @inline-i18n-multi/babel-plugin
465
- ```
466
-
467
- [View Babel plugin →](https://www.npmjs.com/package/@inline-i18n-multi/babel-plugin)
468
-
469
- ### SWC Plugin
470
-
471
- SWC plugin for Next.js 13+ projects. Faster than Babel with the same optimization benefits. Configure in `next.config.js` under `experimental.swcPlugins`.
472
-
473
- ```bash
474
- npm install -D @inline-i18n-multi/swc-plugin
475
- ```
476
-
477
- [View SWC plugin →](https://www.npmjs.com/package/@inline-i18n-multi/swc-plugin)
478
-
479
- ---
480
-
481
779
  ## Developer Tools
482
780
 
483
781
  ### CLI
@@ -504,7 +802,6 @@ npm install -D @inline-i18n-multi/cli
504
802
  **Please read the [full documentation on GitHub](https://github.com/exiivy98/inline-i18n-multi)** for:
505
803
  - Complete API reference
506
804
  - Framework integrations (React, Next.js)
507
- - Build-time optimization
508
805
  - CLI tools
509
806
  - Best practices and examples
510
807
 
package/dist/index.d.mts CHANGED
@@ -61,6 +61,10 @@ interface Config {
61
61
  onMissingTranslation?: WarningHandler;
62
62
  /** Enable debug mode with visual indicators (default: false) */
63
63
  debugMode?: boolean | DebugModeOptions;
64
+ /** Async loader function for lazy loading dictionaries */
65
+ loader?: (locale: Locale, namespace: string) => Promise<Record<string, unknown>>;
66
+ /** Custom handler for missing interpolation variables (v0.6.0) */
67
+ missingVarHandler?: (varName: string, locale: string) => string;
64
68
  }
65
69
 
66
70
  /**
@@ -81,10 +85,9 @@ declare function setLocale(locale: Locale): void;
81
85
  declare function getLocale(): Locale;
82
86
 
83
87
  /**
84
- * Runtime lookup function for plugin-transformed code.
85
- * This is called by code that has been processed by @inline-i18n-multi/babel-plugin
86
- * or @inline-i18n-multi/swc-plugin.
88
+ * Runtime lookup function for build-tool-transformed code.
87
89
  *
90
+ * @deprecated Will be removed in v1.0.0
88
91
  * @param _hash - Content hash (for caching/debugging, unused at runtime)
89
92
  * @param translations - Translation map with locale keys
90
93
  * @param vars - Variables for interpolation
@@ -158,6 +161,24 @@ declare function loadDictionary(locale: Locale, dict: Dictionary, namespace?: st
158
161
  * @param namespace - Optional namespace to clear (clears all if not specified)
159
162
  */
160
163
  declare function clearDictionaries(namespace?: string): void;
164
+ /**
165
+ * Asynchronously load a dictionary using the configured loader
166
+ * @param locale - Locale to load
167
+ * @param namespace - Optional namespace (defaults to 'default')
168
+ * @throws Error if no loader is configured
169
+ *
170
+ * @example
171
+ * configure({ loader: (locale, ns) => import(`./locales/${locale}/${ns}.json`) })
172
+ * await loadAsync('ko', 'dashboard')
173
+ * t('dashboard:title')
174
+ */
175
+ declare function loadAsync(locale: Locale, namespace?: string): Promise<void>;
176
+ /**
177
+ * Check if a dictionary has been loaded for a locale/namespace
178
+ * @param locale - Locale to check
179
+ * @param namespace - Optional namespace (defaults to 'default')
180
+ */
181
+ declare function isLoaded(locale: Locale, namespace?: string): boolean;
161
182
  /**
162
183
  * Translate using key-based lookup (i18n compatible)
163
184
  * @param key - Dot-separated translation key, optionally prefixed with namespace
@@ -191,6 +212,7 @@ declare function getDictionary(locale: Locale, namespace?: string): Dictionary |
191
212
  */
192
213
  declare function getLoadedNamespaces(): string[];
193
214
 
215
+ type FullConfig = Required<Omit<Config, 'loader' | 'missingVarHandler'>> & Pick<Config, 'loader' | 'missingVarHandler'>;
194
216
  /**
195
217
  * Configure inline-i18n-multi settings
196
218
  *
@@ -205,10 +227,80 @@ declare function configure(options: Partial<Config>): void;
205
227
  /**
206
228
  * Get current configuration
207
229
  */
208
- declare function getConfig(): Required<Config>;
230
+ declare function getConfig(): FullConfig;
209
231
  /**
210
232
  * Reset configuration to defaults
211
233
  */
212
234
  declare function resetConfig(): void;
213
235
 
214
- export { type Config, type DebugModeOptions, type Dictionaries, type Dictionary, type Locale, type PluralRules, type TranslationVars, type TranslationWarning, type Translations, type WarningHandler, __i18n_lookup, clearDictionaries, configure, en_de, en_es, en_fr, en_ja, en_zh, getConfig, getDictionary, getLoadedLocales, getLoadedNamespaces, getLocale, hasTranslation, it, it_de, it_es, it_fr, it_ja, it_zh, ja_es, ja_zh, loadDictionaries, loadDictionary, resetConfig, setLocale, t, zh_es };
236
+ type CustomFormatter = (value: unknown, locale: string, style?: string) => string;
237
+ /**
238
+ * Register a custom formatter
239
+ *
240
+ * @example
241
+ * registerFormatter('phone', (value, locale, style?) => {
242
+ * const s = String(value)
243
+ * return `(${s.slice(0,3)}) ${s.slice(3,6)}-${s.slice(6)}`
244
+ * })
245
+ */
246
+ declare function registerFormatter(name: string, formatter: CustomFormatter): void;
247
+ /**
248
+ * Clear all custom formatters
249
+ */
250
+ declare function clearFormatters(): void;
251
+
252
+ type DetectSource = 'navigator' | 'cookie' | 'url' | 'header';
253
+ interface DetectLocaleOptions {
254
+ /** Locales your app supports */
255
+ supportedLocales: Locale[];
256
+ /** Fallback when no source matches */
257
+ defaultLocale: Locale;
258
+ /** Detection sources in priority order (default: ['navigator']) */
259
+ sources?: DetectSource[];
260
+ /** Cookie name to read (default: 'NEXT_LOCALE') */
261
+ cookieName?: string;
262
+ /** Accept-Language header value (for SSR) */
263
+ headerValue?: string;
264
+ }
265
+ /**
266
+ * Auto-detect the user's locale from multiple sources
267
+ *
268
+ * @example
269
+ * const locale = detectLocale({
270
+ * supportedLocales: ['en', 'ko', 'ja'],
271
+ * defaultLocale: 'en',
272
+ * sources: ['cookie', 'navigator'],
273
+ * cookieName: 'NEXT_LOCALE',
274
+ * })
275
+ */
276
+ declare function detectLocale(options: DetectLocaleOptions): Locale;
277
+
278
+ /**
279
+ * Rich Text segment types
280
+ */
281
+ interface RichTextSegment {
282
+ type: 'text' | 'component';
283
+ content: string;
284
+ /** Component name (only for type === 'component') */
285
+ componentName?: string;
286
+ }
287
+ /**
288
+ * Parse a template string into rich text segments.
289
+ * Matches patterns like <name>content</name> for each component name.
290
+ *
291
+ * @param template - The template string with component tags
292
+ * @param componentNames - Array of valid component names to match
293
+ * @returns Array of segments (text or component)
294
+ *
295
+ * @example
296
+ * parseRichText('Read <link>terms</link> and <bold>agree</bold>', ['link', 'bold'])
297
+ * // [
298
+ * // { type: 'text', content: 'Read ' },
299
+ * // { type: 'component', content: 'terms', componentName: 'link' },
300
+ * // { type: 'text', content: ' and ' },
301
+ * // { type: 'component', content: 'agree', componentName: 'bold' },
302
+ * // ]
303
+ */
304
+ declare function parseRichText(template: string, componentNames: string[]): RichTextSegment[];
305
+
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 };
package/dist/index.d.ts CHANGED
@@ -61,6 +61,10 @@ interface Config {
61
61
  onMissingTranslation?: WarningHandler;
62
62
  /** Enable debug mode with visual indicators (default: false) */
63
63
  debugMode?: boolean | DebugModeOptions;
64
+ /** Async loader function for lazy loading dictionaries */
65
+ loader?: (locale: Locale, namespace: string) => Promise<Record<string, unknown>>;
66
+ /** Custom handler for missing interpolation variables (v0.6.0) */
67
+ missingVarHandler?: (varName: string, locale: string) => string;
64
68
  }
65
69
 
66
70
  /**
@@ -81,10 +85,9 @@ declare function setLocale(locale: Locale): void;
81
85
  declare function getLocale(): Locale;
82
86
 
83
87
  /**
84
- * Runtime lookup function for plugin-transformed code.
85
- * This is called by code that has been processed by @inline-i18n-multi/babel-plugin
86
- * or @inline-i18n-multi/swc-plugin.
88
+ * Runtime lookup function for build-tool-transformed code.
87
89
  *
90
+ * @deprecated Will be removed in v1.0.0
88
91
  * @param _hash - Content hash (for caching/debugging, unused at runtime)
89
92
  * @param translations - Translation map with locale keys
90
93
  * @param vars - Variables for interpolation
@@ -158,6 +161,24 @@ declare function loadDictionary(locale: Locale, dict: Dictionary, namespace?: st
158
161
  * @param namespace - Optional namespace to clear (clears all if not specified)
159
162
  */
160
163
  declare function clearDictionaries(namespace?: string): void;
164
+ /**
165
+ * Asynchronously load a dictionary using the configured loader
166
+ * @param locale - Locale to load
167
+ * @param namespace - Optional namespace (defaults to 'default')
168
+ * @throws Error if no loader is configured
169
+ *
170
+ * @example
171
+ * configure({ loader: (locale, ns) => import(`./locales/${locale}/${ns}.json`) })
172
+ * await loadAsync('ko', 'dashboard')
173
+ * t('dashboard:title')
174
+ */
175
+ declare function loadAsync(locale: Locale, namespace?: string): Promise<void>;
176
+ /**
177
+ * Check if a dictionary has been loaded for a locale/namespace
178
+ * @param locale - Locale to check
179
+ * @param namespace - Optional namespace (defaults to 'default')
180
+ */
181
+ declare function isLoaded(locale: Locale, namespace?: string): boolean;
161
182
  /**
162
183
  * Translate using key-based lookup (i18n compatible)
163
184
  * @param key - Dot-separated translation key, optionally prefixed with namespace
@@ -191,6 +212,7 @@ declare function getDictionary(locale: Locale, namespace?: string): Dictionary |
191
212
  */
192
213
  declare function getLoadedNamespaces(): string[];
193
214
 
215
+ type FullConfig = Required<Omit<Config, 'loader' | 'missingVarHandler'>> & Pick<Config, 'loader' | 'missingVarHandler'>;
194
216
  /**
195
217
  * Configure inline-i18n-multi settings
196
218
  *
@@ -205,10 +227,80 @@ declare function configure(options: Partial<Config>): void;
205
227
  /**
206
228
  * Get current configuration
207
229
  */
208
- declare function getConfig(): Required<Config>;
230
+ declare function getConfig(): FullConfig;
209
231
  /**
210
232
  * Reset configuration to defaults
211
233
  */
212
234
  declare function resetConfig(): void;
213
235
 
214
- export { type Config, type DebugModeOptions, type Dictionaries, type Dictionary, type Locale, type PluralRules, type TranslationVars, type TranslationWarning, type Translations, type WarningHandler, __i18n_lookup, clearDictionaries, configure, en_de, en_es, en_fr, en_ja, en_zh, getConfig, getDictionary, getLoadedLocales, getLoadedNamespaces, getLocale, hasTranslation, it, it_de, it_es, it_fr, it_ja, it_zh, ja_es, ja_zh, loadDictionaries, loadDictionary, resetConfig, setLocale, t, zh_es };
236
+ type CustomFormatter = (value: unknown, locale: string, style?: string) => string;
237
+ /**
238
+ * Register a custom formatter
239
+ *
240
+ * @example
241
+ * registerFormatter('phone', (value, locale, style?) => {
242
+ * const s = String(value)
243
+ * return `(${s.slice(0,3)}) ${s.slice(3,6)}-${s.slice(6)}`
244
+ * })
245
+ */
246
+ declare function registerFormatter(name: string, formatter: CustomFormatter): void;
247
+ /**
248
+ * Clear all custom formatters
249
+ */
250
+ declare function clearFormatters(): void;
251
+
252
+ type DetectSource = 'navigator' | 'cookie' | 'url' | 'header';
253
+ interface DetectLocaleOptions {
254
+ /** Locales your app supports */
255
+ supportedLocales: Locale[];
256
+ /** Fallback when no source matches */
257
+ defaultLocale: Locale;
258
+ /** Detection sources in priority order (default: ['navigator']) */
259
+ sources?: DetectSource[];
260
+ /** Cookie name to read (default: 'NEXT_LOCALE') */
261
+ cookieName?: string;
262
+ /** Accept-Language header value (for SSR) */
263
+ headerValue?: string;
264
+ }
265
+ /**
266
+ * Auto-detect the user's locale from multiple sources
267
+ *
268
+ * @example
269
+ * const locale = detectLocale({
270
+ * supportedLocales: ['en', 'ko', 'ja'],
271
+ * defaultLocale: 'en',
272
+ * sources: ['cookie', 'navigator'],
273
+ * cookieName: 'NEXT_LOCALE',
274
+ * })
275
+ */
276
+ declare function detectLocale(options: DetectLocaleOptions): Locale;
277
+
278
+ /**
279
+ * Rich Text segment types
280
+ */
281
+ interface RichTextSegment {
282
+ type: 'text' | 'component';
283
+ content: string;
284
+ /** Component name (only for type === 'component') */
285
+ componentName?: string;
286
+ }
287
+ /**
288
+ * Parse a template string into rich text segments.
289
+ * Matches patterns like <name>content</name> for each component name.
290
+ *
291
+ * @param template - The template string with component tags
292
+ * @param componentNames - Array of valid component names to match
293
+ * @returns Array of segments (text or component)
294
+ *
295
+ * @example
296
+ * parseRichText('Read <link>terms</link> and <bold>agree</bold>', ['link', 'bold'])
297
+ * // [
298
+ * // { type: 'text', content: 'Read ' },
299
+ * // { type: 'component', content: 'terms', componentName: 'link' },
300
+ * // { type: 'text', content: ' and ' },
301
+ * // { type: 'component', content: 'agree', componentName: 'bold' },
302
+ * // ]
303
+ */
304
+ declare function parseRichText(template: string, componentNames: string[]): RichTextSegment[];
305
+
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 };