inline-i18n-multi 0.4.0 → 0.5.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 +111 -1
- package/dist/index.d.mts +51 -2
- package/dist/index.d.ts +51 -2
- package/dist/index.js +159 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +157 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,6 +59,10 @@ 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()`)
|
|
62
66
|
|
|
63
67
|
---
|
|
64
68
|
|
|
@@ -203,7 +207,7 @@ it({
|
|
|
203
207
|
```
|
|
204
208
|
|
|
205
209
|
**Supported ICU styles:**
|
|
206
|
-
- `number`: `decimal`, `percent`, `integer`, `currency`
|
|
210
|
+
- `number`: `decimal`, `percent`, `integer`, `currency`, `compact`, `compactLong`
|
|
207
211
|
- `date`: `short`, `medium`, `long`, `full`
|
|
208
212
|
- `time`: `short`, `medium`, `long`, `full`
|
|
209
213
|
|
|
@@ -234,6 +238,33 @@ it({ en: '{options, list, disjunction}' }, { options: ['A', 'B'] })
|
|
|
234
238
|
// → "A or B"
|
|
235
239
|
```
|
|
236
240
|
|
|
241
|
+
### Currency Formatting
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
it({
|
|
245
|
+
en: 'Total: {price, currency, USD}',
|
|
246
|
+
ko: '합계: {price, currency, KRW}'
|
|
247
|
+
}, { price: 42000 })
|
|
248
|
+
// en → "Total: $42,000.00" / ko → "합계: ₩42,000"
|
|
249
|
+
|
|
250
|
+
// Defaults to USD when currency code omitted
|
|
251
|
+
it({ en: '{price, currency}' }, { price: 100 })
|
|
252
|
+
// → "$100.00"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Compact Number Formatting
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
it({
|
|
259
|
+
en: '{count, number, compact} views',
|
|
260
|
+
ko: '{count, number, compact} 조회'
|
|
261
|
+
}, { count: 1500000 })
|
|
262
|
+
// en → "1.5M views" / ko → "150만 조회"
|
|
263
|
+
|
|
264
|
+
it({ en: '{count, number, compactLong}' }, { count: 1500000 })
|
|
265
|
+
// → "1.5 million"
|
|
266
|
+
```
|
|
267
|
+
|
|
237
268
|
---
|
|
238
269
|
|
|
239
270
|
## Namespace Support
|
|
@@ -278,6 +309,67 @@ t('missing.key') // → "[MISSING: fr] missing.key"
|
|
|
278
309
|
|
|
279
310
|
---
|
|
280
311
|
|
|
312
|
+
## Rich Text Interpolation
|
|
313
|
+
|
|
314
|
+
Embed React components within translations:
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
import { RichText, useRichText } from 'inline-i18n-multi-react'
|
|
318
|
+
|
|
319
|
+
// Component syntax
|
|
320
|
+
<RichText
|
|
321
|
+
translations={{
|
|
322
|
+
en: 'Read <link>terms</link> and <bold>agree</bold>',
|
|
323
|
+
ko: '<link>약관</link>을 읽고 <bold>동의</bold>해주세요'
|
|
324
|
+
}}
|
|
325
|
+
components={{
|
|
326
|
+
link: (text) => <a href="/terms">{text}</a>,
|
|
327
|
+
bold: (text) => <strong>{text}</strong>
|
|
328
|
+
}}
|
|
329
|
+
/>
|
|
330
|
+
|
|
331
|
+
// Hook syntax
|
|
332
|
+
const richT = useRichText({
|
|
333
|
+
link: (text) => <a href="/terms">{text}</a>,
|
|
334
|
+
bold: (text) => <strong>{text}</strong>
|
|
335
|
+
})
|
|
336
|
+
richT({ en: 'Click <link>here</link>', ko: '<link>여기</link> 클릭' })
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Lazy Loading
|
|
342
|
+
|
|
343
|
+
Load dictionaries asynchronously on demand:
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
import { configure, loadAsync, isLoaded, t } from 'inline-i18n-multi'
|
|
347
|
+
|
|
348
|
+
configure({
|
|
349
|
+
loader: (locale, namespace) => import(`./locales/${locale}/${namespace}.json`)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
await loadAsync('ko', 'dashboard')
|
|
353
|
+
t('dashboard:title')
|
|
354
|
+
|
|
355
|
+
isLoaded('ko', 'dashboard') // → true
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### React Hook
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
import { useLoadDictionaries } from 'inline-i18n-multi-react'
|
|
362
|
+
|
|
363
|
+
function Dashboard() {
|
|
364
|
+
const { isLoading, error } = useLoadDictionaries('ko', 'dashboard')
|
|
365
|
+
if (isLoading) return <Spinner />
|
|
366
|
+
if (error) return <Error message={error.message} />
|
|
367
|
+
return <Content />
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
281
373
|
## Configuration
|
|
282
374
|
|
|
283
375
|
Configure global settings for fallback behavior and warnings:
|
|
@@ -366,6 +458,17 @@ Available helpers:
|
|
|
366
458
|
| `configure(options)` | Configure global settings (fallback, warnings, debug) |
|
|
367
459
|
| `getConfig()` | Get current configuration |
|
|
368
460
|
| `resetConfig()` | Reset configuration to defaults |
|
|
461
|
+
| `loadAsync(locale, namespace?)` | Asynchronously load dictionary using configured loader |
|
|
462
|
+
| `isLoaded(locale, namespace?)` | Check if dictionary has been loaded |
|
|
463
|
+
| `parseRichText(template, names)` | Parse rich text template into segments |
|
|
464
|
+
|
|
465
|
+
### React Hooks & Components
|
|
466
|
+
|
|
467
|
+
| Export | Description |
|
|
468
|
+
|--------|-------------|
|
|
469
|
+
| `RichText` | Rich text translation component with embedded components |
|
|
470
|
+
| `useRichText(components)` | Hook returning function for rich text translations |
|
|
471
|
+
| `useLoadDictionaries(locale, ns?)` | Hook for lazy loading dictionaries with loading state |
|
|
369
472
|
|
|
370
473
|
### Types
|
|
371
474
|
|
|
@@ -382,6 +485,7 @@ interface Config {
|
|
|
382
485
|
warnOnMissing?: boolean
|
|
383
486
|
onMissingTranslation?: WarningHandler
|
|
384
487
|
debugMode?: boolean | DebugModeOptions
|
|
488
|
+
loader?: (locale: Locale, namespace: string) => Promise<Record<string, unknown>>
|
|
385
489
|
}
|
|
386
490
|
|
|
387
491
|
interface DebugModeOptions {
|
|
@@ -400,6 +504,12 @@ interface TranslationWarning {
|
|
|
400
504
|
}
|
|
401
505
|
|
|
402
506
|
type WarningHandler = (warning: TranslationWarning) => void
|
|
507
|
+
|
|
508
|
+
interface RichTextSegment {
|
|
509
|
+
type: 'text' | 'component'
|
|
510
|
+
content: string
|
|
511
|
+
componentName?: string
|
|
512
|
+
}
|
|
403
513
|
```
|
|
404
514
|
|
|
405
515
|
---
|
package/dist/index.d.mts
CHANGED
|
@@ -61,6 +61,8 @@ 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>>;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
/**
|
|
@@ -158,6 +160,24 @@ declare function loadDictionary(locale: Locale, dict: Dictionary, namespace?: st
|
|
|
158
160
|
* @param namespace - Optional namespace to clear (clears all if not specified)
|
|
159
161
|
*/
|
|
160
162
|
declare function clearDictionaries(namespace?: string): void;
|
|
163
|
+
/**
|
|
164
|
+
* Asynchronously load a dictionary using the configured loader
|
|
165
|
+
* @param locale - Locale to load
|
|
166
|
+
* @param namespace - Optional namespace (defaults to 'default')
|
|
167
|
+
* @throws Error if no loader is configured
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* configure({ loader: (locale, ns) => import(`./locales/${locale}/${ns}.json`) })
|
|
171
|
+
* await loadAsync('ko', 'dashboard')
|
|
172
|
+
* t('dashboard:title')
|
|
173
|
+
*/
|
|
174
|
+
declare function loadAsync(locale: Locale, namespace?: string): Promise<void>;
|
|
175
|
+
/**
|
|
176
|
+
* Check if a dictionary has been loaded for a locale/namespace
|
|
177
|
+
* @param locale - Locale to check
|
|
178
|
+
* @param namespace - Optional namespace (defaults to 'default')
|
|
179
|
+
*/
|
|
180
|
+
declare function isLoaded(locale: Locale, namespace?: string): boolean;
|
|
161
181
|
/**
|
|
162
182
|
* Translate using key-based lookup (i18n compatible)
|
|
163
183
|
* @param key - Dot-separated translation key, optionally prefixed with namespace
|
|
@@ -191,6 +211,7 @@ declare function getDictionary(locale: Locale, namespace?: string): Dictionary |
|
|
|
191
211
|
*/
|
|
192
212
|
declare function getLoadedNamespaces(): string[];
|
|
193
213
|
|
|
214
|
+
type FullConfig = Required<Omit<Config, 'loader'>> & Pick<Config, 'loader'>;
|
|
194
215
|
/**
|
|
195
216
|
* Configure inline-i18n-multi settings
|
|
196
217
|
*
|
|
@@ -205,10 +226,38 @@ declare function configure(options: Partial<Config>): void;
|
|
|
205
226
|
/**
|
|
206
227
|
* Get current configuration
|
|
207
228
|
*/
|
|
208
|
-
declare function getConfig():
|
|
229
|
+
declare function getConfig(): FullConfig;
|
|
209
230
|
/**
|
|
210
231
|
* Reset configuration to defaults
|
|
211
232
|
*/
|
|
212
233
|
declare function resetConfig(): void;
|
|
213
234
|
|
|
214
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Rich Text segment types
|
|
237
|
+
*/
|
|
238
|
+
interface RichTextSegment {
|
|
239
|
+
type: 'text' | 'component';
|
|
240
|
+
content: string;
|
|
241
|
+
/** Component name (only for type === 'component') */
|
|
242
|
+
componentName?: string;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Parse a template string into rich text segments.
|
|
246
|
+
* Matches patterns like <name>content</name> for each component name.
|
|
247
|
+
*
|
|
248
|
+
* @param template - The template string with component tags
|
|
249
|
+
* @param componentNames - Array of valid component names to match
|
|
250
|
+
* @returns Array of segments (text or component)
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* parseRichText('Read <link>terms</link> and <bold>agree</bold>', ['link', 'bold'])
|
|
254
|
+
* // [
|
|
255
|
+
* // { type: 'text', content: 'Read ' },
|
|
256
|
+
* // { type: 'component', content: 'terms', componentName: 'link' },
|
|
257
|
+
* // { type: 'text', content: ' and ' },
|
|
258
|
+
* // { type: 'component', content: 'agree', componentName: 'bold' },
|
|
259
|
+
* // ]
|
|
260
|
+
*/
|
|
261
|
+
declare function parseRichText(template: string, componentNames: string[]): RichTextSegment[];
|
|
262
|
+
|
|
263
|
+
export { type Config, type DebugModeOptions, type Dictionaries, type Dictionary, type Locale, type PluralRules, type RichTextSegment, 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, isLoaded, it, it_de, it_es, it_fr, it_ja, it_zh, ja_es, ja_zh, loadAsync, loadDictionaries, loadDictionary, parseRichText, resetConfig, setLocale, t, zh_es };
|
package/dist/index.d.ts
CHANGED
|
@@ -61,6 +61,8 @@ 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>>;
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
/**
|
|
@@ -158,6 +160,24 @@ declare function loadDictionary(locale: Locale, dict: Dictionary, namespace?: st
|
|
|
158
160
|
* @param namespace - Optional namespace to clear (clears all if not specified)
|
|
159
161
|
*/
|
|
160
162
|
declare function clearDictionaries(namespace?: string): void;
|
|
163
|
+
/**
|
|
164
|
+
* Asynchronously load a dictionary using the configured loader
|
|
165
|
+
* @param locale - Locale to load
|
|
166
|
+
* @param namespace - Optional namespace (defaults to 'default')
|
|
167
|
+
* @throws Error if no loader is configured
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* configure({ loader: (locale, ns) => import(`./locales/${locale}/${ns}.json`) })
|
|
171
|
+
* await loadAsync('ko', 'dashboard')
|
|
172
|
+
* t('dashboard:title')
|
|
173
|
+
*/
|
|
174
|
+
declare function loadAsync(locale: Locale, namespace?: string): Promise<void>;
|
|
175
|
+
/**
|
|
176
|
+
* Check if a dictionary has been loaded for a locale/namespace
|
|
177
|
+
* @param locale - Locale to check
|
|
178
|
+
* @param namespace - Optional namespace (defaults to 'default')
|
|
179
|
+
*/
|
|
180
|
+
declare function isLoaded(locale: Locale, namespace?: string): boolean;
|
|
161
181
|
/**
|
|
162
182
|
* Translate using key-based lookup (i18n compatible)
|
|
163
183
|
* @param key - Dot-separated translation key, optionally prefixed with namespace
|
|
@@ -191,6 +211,7 @@ declare function getDictionary(locale: Locale, namespace?: string): Dictionary |
|
|
|
191
211
|
*/
|
|
192
212
|
declare function getLoadedNamespaces(): string[];
|
|
193
213
|
|
|
214
|
+
type FullConfig = Required<Omit<Config, 'loader'>> & Pick<Config, 'loader'>;
|
|
194
215
|
/**
|
|
195
216
|
* Configure inline-i18n-multi settings
|
|
196
217
|
*
|
|
@@ -205,10 +226,38 @@ declare function configure(options: Partial<Config>): void;
|
|
|
205
226
|
/**
|
|
206
227
|
* Get current configuration
|
|
207
228
|
*/
|
|
208
|
-
declare function getConfig():
|
|
229
|
+
declare function getConfig(): FullConfig;
|
|
209
230
|
/**
|
|
210
231
|
* Reset configuration to defaults
|
|
211
232
|
*/
|
|
212
233
|
declare function resetConfig(): void;
|
|
213
234
|
|
|
214
|
-
|
|
235
|
+
/**
|
|
236
|
+
* Rich Text segment types
|
|
237
|
+
*/
|
|
238
|
+
interface RichTextSegment {
|
|
239
|
+
type: 'text' | 'component';
|
|
240
|
+
content: string;
|
|
241
|
+
/** Component name (only for type === 'component') */
|
|
242
|
+
componentName?: string;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Parse a template string into rich text segments.
|
|
246
|
+
* Matches patterns like <name>content</name> for each component name.
|
|
247
|
+
*
|
|
248
|
+
* @param template - The template string with component tags
|
|
249
|
+
* @param componentNames - Array of valid component names to match
|
|
250
|
+
* @returns Array of segments (text or component)
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* parseRichText('Read <link>terms</link> and <bold>agree</bold>', ['link', 'bold'])
|
|
254
|
+
* // [
|
|
255
|
+
* // { type: 'text', content: 'Read ' },
|
|
256
|
+
* // { type: 'component', content: 'terms', componentName: 'link' },
|
|
257
|
+
* // { type: 'text', content: ' and ' },
|
|
258
|
+
* // { type: 'component', content: 'agree', componentName: 'bold' },
|
|
259
|
+
* // ]
|
|
260
|
+
*/
|
|
261
|
+
declare function parseRichText(template: string, componentNames: string[]): RichTextSegment[];
|
|
262
|
+
|
|
263
|
+
export { type Config, type DebugModeOptions, type Dictionaries, type Dictionary, type Locale, type PluralRules, type RichTextSegment, 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, isLoaded, it, it_de, it_es, it_fr, it_ja, it_zh, ja_es, ja_zh, loadAsync, loadDictionaries, loadDictionary, parseRichText, resetConfig, setLocale, t, zh_es };
|
package/dist/index.js
CHANGED
|
@@ -115,6 +115,67 @@ function formatTimeElement(el, vars, locale) {
|
|
|
115
115
|
return `{${el.value}}`;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
+
var CURRENCY_PATTERN = /\{(\w+),\s*currency(?:,\s*(\w+))?\}/g;
|
|
119
|
+
function preprocessCurrency(template) {
|
|
120
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
121
|
+
let counter = 0;
|
|
122
|
+
const processed = template.replace(CURRENCY_PATTERN, (_, variable, currencyCode) => {
|
|
123
|
+
const placeholder = `__CURRENCY_${counter++}__`;
|
|
124
|
+
replacements.set(placeholder, { variable, currencyCode: currencyCode || "USD" });
|
|
125
|
+
return `{${placeholder}}`;
|
|
126
|
+
});
|
|
127
|
+
return { processed, replacements };
|
|
128
|
+
}
|
|
129
|
+
function formatCurrencyValue(variableName, currencyCode, vars, locale) {
|
|
130
|
+
const value = vars[variableName];
|
|
131
|
+
if (value === void 0) {
|
|
132
|
+
return `{${variableName}}`;
|
|
133
|
+
}
|
|
134
|
+
const num = typeof value === "number" ? value : Number(value);
|
|
135
|
+
if (isNaN(num)) {
|
|
136
|
+
return `{${variableName}}`;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
return new Intl.NumberFormat(locale, {
|
|
140
|
+
style: "currency",
|
|
141
|
+
currency: currencyCode
|
|
142
|
+
}).format(num);
|
|
143
|
+
} catch {
|
|
144
|
+
return String(num);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
var COMPACT_NUMBER_PATTERN = /\{(\w+),\s*number,\s*(compact|compactLong)\}/g;
|
|
148
|
+
function preprocessCompactNumber(template) {
|
|
149
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
150
|
+
let counter = 0;
|
|
151
|
+
const processed = template.replace(COMPACT_NUMBER_PATTERN, (_, variable, style) => {
|
|
152
|
+
const placeholder = `__COMPACT_${counter++}__`;
|
|
153
|
+
replacements.set(placeholder, {
|
|
154
|
+
variable,
|
|
155
|
+
display: style === "compactLong" ? "long" : "short"
|
|
156
|
+
});
|
|
157
|
+
return `{${placeholder}}`;
|
|
158
|
+
});
|
|
159
|
+
return { processed, replacements };
|
|
160
|
+
}
|
|
161
|
+
function formatCompactNumber(variableName, display, vars, locale) {
|
|
162
|
+
const value = vars[variableName];
|
|
163
|
+
if (value === void 0) {
|
|
164
|
+
return `{${variableName}}`;
|
|
165
|
+
}
|
|
166
|
+
const num = typeof value === "number" ? value : Number(value);
|
|
167
|
+
if (isNaN(num)) {
|
|
168
|
+
return `{${variableName}}`;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
return new Intl.NumberFormat(locale, {
|
|
172
|
+
notation: "compact",
|
|
173
|
+
compactDisplay: display
|
|
174
|
+
}).format(num);
|
|
175
|
+
} catch {
|
|
176
|
+
return String(num);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
118
179
|
function getRelativeTimeUnit(date, now = /* @__PURE__ */ new Date()) {
|
|
119
180
|
const diffMs = date.getTime() - now.getTime();
|
|
120
181
|
const diffSeconds = Math.round(diffMs / 1e3);
|
|
@@ -194,10 +255,20 @@ function formatListValue(variableName, type, style, vars, locale) {
|
|
|
194
255
|
}
|
|
195
256
|
}
|
|
196
257
|
function interpolateICU(template, vars, locale) {
|
|
197
|
-
const { processed:
|
|
258
|
+
const { processed: afterCurrency, replacements: currencyReplacements } = preprocessCurrency(template);
|
|
259
|
+
const { processed: afterCompact, replacements: compactReplacements } = preprocessCompactNumber(afterCurrency);
|
|
260
|
+
const { processed: afterRelTime, replacements: relTimeReplacements } = preprocessRelativeTime(afterCompact);
|
|
198
261
|
const { processed: afterList, replacements: listReplacements } = preprocessList(afterRelTime);
|
|
199
262
|
const ast = icuMessageformatParser.parse(afterList);
|
|
200
263
|
let result = formatElements(ast, vars, locale, null);
|
|
264
|
+
for (const [placeholder, { variable, currencyCode }] of currencyReplacements) {
|
|
265
|
+
const formatted = formatCurrencyValue(variable, currencyCode, vars, locale);
|
|
266
|
+
result = result.replace(`{${placeholder}}`, formatted);
|
|
267
|
+
}
|
|
268
|
+
for (const [placeholder, { variable, display }] of compactReplacements) {
|
|
269
|
+
const formatted = formatCompactNumber(variable, display, vars, locale);
|
|
270
|
+
result = result.replace(`{${placeholder}}`, formatted);
|
|
271
|
+
}
|
|
201
272
|
for (const [placeholder, { variable, style }] of relTimeReplacements) {
|
|
202
273
|
const formatted = formatRelativeTimeValue(variable, style, vars, locale);
|
|
203
274
|
result = result.replace(`{${placeholder}}`, formatted);
|
|
@@ -270,7 +341,7 @@ function formatSelect(el, vars, locale) {
|
|
|
270
341
|
}
|
|
271
342
|
return `{${el.value}}`;
|
|
272
343
|
}
|
|
273
|
-
var ICU_PATTERN = /\{[^}]+,\s*(plural|select|selectordinal|number|date|time|relativeTime|list)\s*[,}]/;
|
|
344
|
+
var ICU_PATTERN = /\{[^}]+,\s*(plural|select|selectordinal|number|date|time|relativeTime|list|currency)\s*[,}]/;
|
|
274
345
|
function hasICUPattern(template) {
|
|
275
346
|
return ICU_PATTERN.test(template);
|
|
276
347
|
}
|
|
@@ -318,7 +389,8 @@ var defaultConfig = {
|
|
|
318
389
|
fallbackChain: {},
|
|
319
390
|
warnOnMissing: isDevMode(),
|
|
320
391
|
onMissingTranslation: defaultWarningHandler,
|
|
321
|
-
debugMode: false
|
|
392
|
+
debugMode: false,
|
|
393
|
+
loader: void 0
|
|
322
394
|
};
|
|
323
395
|
var config = { ...defaultConfig };
|
|
324
396
|
function configure(options) {
|
|
@@ -521,6 +593,11 @@ var zh_es = createPair("zh", "es");
|
|
|
521
593
|
var DEFAULT_NAMESPACE = "default";
|
|
522
594
|
var NAMESPACE_SEPARATOR = ":";
|
|
523
595
|
var namespacedDictionaries = {};
|
|
596
|
+
var loadingState = {};
|
|
597
|
+
var loadingPromises = /* @__PURE__ */ new Map();
|
|
598
|
+
function getLoadingKey(locale, namespace) {
|
|
599
|
+
return `${namespace}:${locale}`;
|
|
600
|
+
}
|
|
524
601
|
function parseKey(fullKey) {
|
|
525
602
|
const separatorIndex = fullKey.indexOf(NAMESPACE_SEPARATOR);
|
|
526
603
|
if (separatorIndex > 0) {
|
|
@@ -557,10 +634,50 @@ function loadDictionary(locale, dict, namespace) {
|
|
|
557
634
|
function clearDictionaries(namespace) {
|
|
558
635
|
if (namespace) {
|
|
559
636
|
delete namespacedDictionaries[namespace];
|
|
637
|
+
for (const key of Object.keys(loadingState)) {
|
|
638
|
+
if (key.startsWith(`${namespace}:`)) {
|
|
639
|
+
delete loadingState[key];
|
|
640
|
+
loadingPromises.delete(key);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
560
643
|
} else {
|
|
561
644
|
namespacedDictionaries = {};
|
|
645
|
+
loadingState = {};
|
|
646
|
+
loadingPromises.clear();
|
|
562
647
|
}
|
|
563
648
|
}
|
|
649
|
+
async function loadAsync(locale, namespace) {
|
|
650
|
+
const ns = namespace || DEFAULT_NAMESPACE;
|
|
651
|
+
const cfg = getConfig();
|
|
652
|
+
if (!cfg.loader) {
|
|
653
|
+
throw new Error("No loader configured. Call configure({ loader: ... }) first.");
|
|
654
|
+
}
|
|
655
|
+
const key = getLoadingKey(locale, ns);
|
|
656
|
+
if (loadingState[key] === "loaded") return;
|
|
657
|
+
if (loadingPromises.has(key)) {
|
|
658
|
+
return loadingPromises.get(key);
|
|
659
|
+
}
|
|
660
|
+
const promise = (async () => {
|
|
661
|
+
loadingState[key] = "loading";
|
|
662
|
+
try {
|
|
663
|
+
const dict = await cfg.loader(locale, ns);
|
|
664
|
+
loadDictionary(locale, dict, ns);
|
|
665
|
+
loadingState[key] = "loaded";
|
|
666
|
+
} catch (error) {
|
|
667
|
+
loadingState[key] = "error";
|
|
668
|
+
throw error;
|
|
669
|
+
} finally {
|
|
670
|
+
loadingPromises.delete(key);
|
|
671
|
+
}
|
|
672
|
+
})();
|
|
673
|
+
loadingPromises.set(key, promise);
|
|
674
|
+
return promise;
|
|
675
|
+
}
|
|
676
|
+
function isLoaded(locale, namespace) {
|
|
677
|
+
const ns = namespace || DEFAULT_NAMESPACE;
|
|
678
|
+
const key = getLoadingKey(locale, ns);
|
|
679
|
+
return loadingState[key] === "loaded";
|
|
680
|
+
}
|
|
564
681
|
function getNestedValue(dict, key) {
|
|
565
682
|
const parts = key.split(".");
|
|
566
683
|
let current = dict;
|
|
@@ -670,6 +787,42 @@ function getLoadedNamespaces() {
|
|
|
670
787
|
return Object.keys(namespacedDictionaries);
|
|
671
788
|
}
|
|
672
789
|
|
|
790
|
+
// src/richtext.ts
|
|
791
|
+
function escapeRegExp(str) {
|
|
792
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
793
|
+
}
|
|
794
|
+
function parseRichText(template, componentNames) {
|
|
795
|
+
if (componentNames.length === 0) {
|
|
796
|
+
return [{ type: "text", content: template }];
|
|
797
|
+
}
|
|
798
|
+
const segments = [];
|
|
799
|
+
const namesPattern = componentNames.map(escapeRegExp).join("|");
|
|
800
|
+
const regex = new RegExp(`<(${namesPattern})>(.*?)</\\1>`, "gs");
|
|
801
|
+
let lastIndex = 0;
|
|
802
|
+
let match;
|
|
803
|
+
while ((match = regex.exec(template)) !== null) {
|
|
804
|
+
if (match.index > lastIndex) {
|
|
805
|
+
segments.push({
|
|
806
|
+
type: "text",
|
|
807
|
+
content: template.slice(lastIndex, match.index)
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
segments.push({
|
|
811
|
+
type: "component",
|
|
812
|
+
content: match[2] ?? "",
|
|
813
|
+
componentName: match[1]
|
|
814
|
+
});
|
|
815
|
+
lastIndex = match.index + match[0].length;
|
|
816
|
+
}
|
|
817
|
+
if (lastIndex < template.length) {
|
|
818
|
+
segments.push({
|
|
819
|
+
type: "text",
|
|
820
|
+
content: template.slice(lastIndex)
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
return segments;
|
|
824
|
+
}
|
|
825
|
+
|
|
673
826
|
exports.__i18n_lookup = __i18n_lookup;
|
|
674
827
|
exports.clearDictionaries = clearDictionaries;
|
|
675
828
|
exports.configure = configure;
|
|
@@ -684,6 +837,7 @@ exports.getLoadedLocales = getLoadedLocales;
|
|
|
684
837
|
exports.getLoadedNamespaces = getLoadedNamespaces;
|
|
685
838
|
exports.getLocale = getLocale;
|
|
686
839
|
exports.hasTranslation = hasTranslation;
|
|
840
|
+
exports.isLoaded = isLoaded;
|
|
687
841
|
exports.it = it;
|
|
688
842
|
exports.it_de = it_de;
|
|
689
843
|
exports.it_es = it_es;
|
|
@@ -692,8 +846,10 @@ exports.it_ja = it_ja;
|
|
|
692
846
|
exports.it_zh = it_zh;
|
|
693
847
|
exports.ja_es = ja_es;
|
|
694
848
|
exports.ja_zh = ja_zh;
|
|
849
|
+
exports.loadAsync = loadAsync;
|
|
695
850
|
exports.loadDictionaries = loadDictionaries;
|
|
696
851
|
exports.loadDictionary = loadDictionary;
|
|
852
|
+
exports.parseRichText = parseRichText;
|
|
697
853
|
exports.resetConfig = resetConfig;
|
|
698
854
|
exports.setLocale = setLocale;
|
|
699
855
|
exports.t = t;
|