inline-i18n-multi 0.7.0 → 0.9.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 +124 -1
- package/dist/index.d.mts +22 -3
- package/dist/index.d.ts +22 -3
- package/dist/index.js +36 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +36 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,6 +70,12 @@ See "Hello" in your app? Just search for "Hello" in your codebase. **Done.**
|
|
|
70
70
|
- **ICU Message Cache** - Memoize parsed ICU ASTs for performance (`icuCacheSize`, `clearICUCache()`)
|
|
71
71
|
- **Plural Shorthand** - Concise plural syntax (`{count, p, item|items}`)
|
|
72
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
|
|
76
|
+
- **Context System** - Contextual translation disambiguation (`t('greeting', { _context: 'formal' })` with `key#context` dictionary keys)
|
|
77
|
+
- **Translation Extraction** - Extract inline translations to JSON files (`npx inline-i18n extract`)
|
|
78
|
+
- **CLI Watch Mode** - `--watch` flag for `validate` and `typegen` commands
|
|
73
79
|
|
|
74
80
|
---
|
|
75
81
|
|
|
@@ -657,6 +663,122 @@ setLocale('ja') // saves 'ja' to localStorage
|
|
|
657
663
|
|
|
658
664
|
---
|
|
659
665
|
|
|
666
|
+
## Translation Scope
|
|
667
|
+
|
|
668
|
+
Create a scoped translation function bound to a specific namespace with `createScope`:
|
|
669
|
+
|
|
670
|
+
```typescript
|
|
671
|
+
import { createScope, loadDictionaries } from 'inline-i18n-multi'
|
|
672
|
+
|
|
673
|
+
loadDictionaries({ en: { greeting: 'Hello' }, ko: { greeting: '안녕하세요' } }, 'common')
|
|
674
|
+
|
|
675
|
+
const tc = createScope('common')
|
|
676
|
+
tc('greeting') // → "Hello"
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
Eliminates the need to write namespace prefixes (`t('common:greeting')`) every time, keeping your code cleaner.
|
|
680
|
+
|
|
681
|
+
---
|
|
682
|
+
|
|
683
|
+
## Unused Key Detection
|
|
684
|
+
|
|
685
|
+
Use the `--unused` flag to detect translation keys defined in dictionaries but not used in your code:
|
|
686
|
+
|
|
687
|
+
```bash
|
|
688
|
+
npx inline-i18n validate --unused
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
Helps identify and clean up translations that are no longer needed.
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
|
|
695
|
+
## TypeScript Type Generation
|
|
696
|
+
|
|
697
|
+
Auto-generate TypeScript type definition files for your translation keys with the `typegen` command:
|
|
698
|
+
|
|
699
|
+
```bash
|
|
700
|
+
npx inline-i18n typegen --output src/i18n.d.ts
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
The generated types enable autocomplete and type checking for `t()` function key arguments.
|
|
704
|
+
|
|
705
|
+
---
|
|
706
|
+
|
|
707
|
+
## Context System
|
|
708
|
+
|
|
709
|
+
Use the `_context` parameter to disambiguate translations for the same key based on context. Dictionary keys use the `key#context` format:
|
|
710
|
+
|
|
711
|
+
```typescript
|
|
712
|
+
import { t, loadDictionaries } from 'inline-i18n-multi'
|
|
713
|
+
|
|
714
|
+
loadDictionaries({
|
|
715
|
+
en: {
|
|
716
|
+
greeting: 'Hi',
|
|
717
|
+
'greeting#formal': 'Good day',
|
|
718
|
+
'greeting#casual': 'Hey',
|
|
719
|
+
},
|
|
720
|
+
ko: {
|
|
721
|
+
greeting: '안녕하세요',
|
|
722
|
+
'greeting#formal': '안녕하십니까',
|
|
723
|
+
'greeting#casual': '야',
|
|
724
|
+
}
|
|
725
|
+
})
|
|
726
|
+
|
|
727
|
+
// No context (default)
|
|
728
|
+
t('greeting') // → "Hi"
|
|
729
|
+
|
|
730
|
+
// Formal context
|
|
731
|
+
t('greeting', { _context: 'formal' }) // → "Good day"
|
|
732
|
+
|
|
733
|
+
// Casual context
|
|
734
|
+
t('greeting', { _context: 'casual' }) // → "Hey"
|
|
735
|
+
|
|
736
|
+
// Falls back to the uncontexted key when a contexted key is not found
|
|
737
|
+
t('greeting', { _context: 'unknown' }) // → "Hi"
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
Useful when the same translation key has different meanings depending on context (e.g., "open" used as both a verb and an adjective).
|
|
741
|
+
|
|
742
|
+
---
|
|
743
|
+
|
|
744
|
+
## Translation Extraction
|
|
745
|
+
|
|
746
|
+
Extract inline translations from source code into JSON files using the `extract` command:
|
|
747
|
+
|
|
748
|
+
```bash
|
|
749
|
+
# Default (outputs to ./locales directory)
|
|
750
|
+
npx inline-i18n extract
|
|
751
|
+
|
|
752
|
+
# Specify output directory
|
|
753
|
+
npx inline-i18n extract --output ./src/locales
|
|
754
|
+
|
|
755
|
+
# Specify locales
|
|
756
|
+
npx inline-i18n extract --locales en,ko,ja
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
Scans `it()` calls in your source code and generates per-locale JSON files. Existing JSON files are preserved, with only new keys being added.
|
|
760
|
+
|
|
761
|
+
---
|
|
762
|
+
|
|
763
|
+
## CLI Watch Mode
|
|
764
|
+
|
|
765
|
+
Add the `--watch` flag to `validate` and `typegen` commands to automatically re-run on file changes:
|
|
766
|
+
|
|
767
|
+
```bash
|
|
768
|
+
# Watch mode for validation
|
|
769
|
+
npx inline-i18n validate --watch
|
|
770
|
+
|
|
771
|
+
# Watch mode for type generation
|
|
772
|
+
npx inline-i18n typegen --output src/i18n.d.ts --watch
|
|
773
|
+
|
|
774
|
+
# Combine with strict mode
|
|
775
|
+
npx inline-i18n validate --strict --watch
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
Provides instant feedback during development by re-running validation or type generation each time a file is saved.
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
660
782
|
## Configuration
|
|
661
783
|
|
|
662
784
|
Configure global settings for fallback behavior and warnings:
|
|
@@ -750,6 +872,7 @@ Available helpers:
|
|
|
750
872
|
| `parseRichText(template, names)` | Parse rich text template into segments |
|
|
751
873
|
| `clearICUCache()` | Clear the ICU message AST cache |
|
|
752
874
|
| `restoreLocale()` | Restore locale from configured persistent storage (cookie or localStorage) |
|
|
875
|
+
| `createScope(namespace)` | Return a translation function scoped to the given namespace |
|
|
753
876
|
|
|
754
877
|
### Custom Formatters
|
|
755
878
|
|
|
@@ -899,7 +1022,7 @@ npm install inline-i18n-multi-next
|
|
|
899
1022
|
|
|
900
1023
|
### CLI
|
|
901
1024
|
|
|
902
|
-
Command-line tools for translation management. Find translations with `inline-i18n find "text"`, validate consistency with `inline-i18n validate`, and generate coverage reports with `inline-i18n coverage`.
|
|
1025
|
+
Command-line tools for translation management. Find translations with `inline-i18n find "text"`, validate consistency with `inline-i18n validate`, extract inline translations with `inline-i18n extract`, and generate coverage reports with `inline-i18n coverage`. Supports `--watch` mode for `validate` and `typegen`.
|
|
903
1026
|
|
|
904
1027
|
```bash
|
|
905
1028
|
npm install -D @inline-i18n-multi/cli
|
package/dist/index.d.mts
CHANGED
|
@@ -10,8 +10,11 @@ type Translations = Record<Locale, string>;
|
|
|
10
10
|
* Variables for interpolation
|
|
11
11
|
* Supports string, number, Date values for ICU formatting
|
|
12
12
|
* Supports string[] for list formatting
|
|
13
|
+
* Use _context for contextual translation disambiguation (v0.9.0)
|
|
13
14
|
*/
|
|
14
|
-
type TranslationVars = Record<string, string | number | Date | string[]
|
|
15
|
+
type TranslationVars = Record<string, string | number | Date | string[]> & {
|
|
16
|
+
_context?: string;
|
|
17
|
+
};
|
|
15
18
|
/**
|
|
16
19
|
* Warning information for missing translations
|
|
17
20
|
*/
|
|
@@ -210,8 +213,9 @@ declare function t(key: string, vars?: TranslationVars, locale?: Locale): string
|
|
|
210
213
|
* Check if a translation key exists
|
|
211
214
|
* @param key - Translation key (may include namespace prefix)
|
|
212
215
|
* @param locale - Optional locale to check
|
|
216
|
+
* @param context - Optional context for contextual translations (v0.9.0)
|
|
213
217
|
*/
|
|
214
|
-
declare function hasTranslation(key: string, locale?: Locale): boolean;
|
|
218
|
+
declare function hasTranslation(key: string, locale?: Locale, context?: string): boolean;
|
|
215
219
|
/**
|
|
216
220
|
* Get all loaded locales
|
|
217
221
|
* @param namespace - Optional namespace (returns from all if not specified)
|
|
@@ -295,6 +299,21 @@ interface DetectLocaleOptions {
|
|
|
295
299
|
*/
|
|
296
300
|
declare function detectLocale(options: DetectLocaleOptions): Locale;
|
|
297
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Create a scoped translation function for a given namespace.
|
|
304
|
+
* Returns a `t()` wrapper that auto-prepends the namespace prefix.
|
|
305
|
+
*
|
|
306
|
+
* @param namespace - Namespace to scope to
|
|
307
|
+
* @returns Scoped translation function
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* const tc = createScope('common')
|
|
311
|
+
* tc('greeting') // equivalent to t('common:greeting')
|
|
312
|
+
* tc('nav.home') // equivalent to t('common:nav.home')
|
|
313
|
+
* tc('welcome', { name: 'John' })
|
|
314
|
+
*/
|
|
315
|
+
declare function createScope(namespace: string): (key: string, vars?: TranslationVars, locale?: Locale) => string;
|
|
316
|
+
|
|
298
317
|
/**
|
|
299
318
|
* Rich Text segment types
|
|
300
319
|
*/
|
|
@@ -323,4 +342,4 @@ interface RichTextSegment {
|
|
|
323
342
|
*/
|
|
324
343
|
declare function parseRichText(template: string, componentNames: string[]): RichTextSegment[];
|
|
325
344
|
|
|
326
|
-
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, 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 };
|
|
345
|
+
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
|
@@ -10,8 +10,11 @@ type Translations = Record<Locale, string>;
|
|
|
10
10
|
* Variables for interpolation
|
|
11
11
|
* Supports string, number, Date values for ICU formatting
|
|
12
12
|
* Supports string[] for list formatting
|
|
13
|
+
* Use _context for contextual translation disambiguation (v0.9.0)
|
|
13
14
|
*/
|
|
14
|
-
type TranslationVars = Record<string, string | number | Date | string[]
|
|
15
|
+
type TranslationVars = Record<string, string | number | Date | string[]> & {
|
|
16
|
+
_context?: string;
|
|
17
|
+
};
|
|
15
18
|
/**
|
|
16
19
|
* Warning information for missing translations
|
|
17
20
|
*/
|
|
@@ -210,8 +213,9 @@ declare function t(key: string, vars?: TranslationVars, locale?: Locale): string
|
|
|
210
213
|
* Check if a translation key exists
|
|
211
214
|
* @param key - Translation key (may include namespace prefix)
|
|
212
215
|
* @param locale - Optional locale to check
|
|
216
|
+
* @param context - Optional context for contextual translations (v0.9.0)
|
|
213
217
|
*/
|
|
214
|
-
declare function hasTranslation(key: string, locale?: Locale): boolean;
|
|
218
|
+
declare function hasTranslation(key: string, locale?: Locale, context?: string): boolean;
|
|
215
219
|
/**
|
|
216
220
|
* Get all loaded locales
|
|
217
221
|
* @param namespace - Optional namespace (returns from all if not specified)
|
|
@@ -295,6 +299,21 @@ interface DetectLocaleOptions {
|
|
|
295
299
|
*/
|
|
296
300
|
declare function detectLocale(options: DetectLocaleOptions): Locale;
|
|
297
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Create a scoped translation function for a given namespace.
|
|
304
|
+
* Returns a `t()` wrapper that auto-prepends the namespace prefix.
|
|
305
|
+
*
|
|
306
|
+
* @param namespace - Namespace to scope to
|
|
307
|
+
* @returns Scoped translation function
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* const tc = createScope('common')
|
|
311
|
+
* tc('greeting') // equivalent to t('common:greeting')
|
|
312
|
+
* tc('nav.home') // equivalent to t('common:nav.home')
|
|
313
|
+
* tc('welcome', { name: 'John' })
|
|
314
|
+
*/
|
|
315
|
+
declare function createScope(namespace: string): (key: string, vars?: TranslationVars, locale?: Locale) => string;
|
|
316
|
+
|
|
298
317
|
/**
|
|
299
318
|
* Rich Text segment types
|
|
300
319
|
*/
|
|
@@ -323,4 +342,4 @@ interface RichTextSegment {
|
|
|
323
342
|
*/
|
|
324
343
|
declare function parseRichText(template: string, componentNames: string[]): RichTextSegment[];
|
|
325
344
|
|
|
326
|
-
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, 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 };
|
|
345
|
+
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
|
@@ -603,19 +603,25 @@ function hasICUPattern(template) {
|
|
|
603
603
|
|
|
604
604
|
// src/interpolation.ts
|
|
605
605
|
var VARIABLE_PATTERN = /\{(\w+)\}/g;
|
|
606
|
+
function stripContext(vars) {
|
|
607
|
+
if (!("_context" in vars)) return vars;
|
|
608
|
+
const { _context: _, ...rest } = vars;
|
|
609
|
+
return rest;
|
|
610
|
+
}
|
|
606
611
|
function interpolate(template, vars, locale) {
|
|
607
612
|
const resolvedLocale = locale || "en";
|
|
613
|
+
const cleanVars = vars ? stripContext(vars) : vars;
|
|
608
614
|
if (hasICUPattern(template) || hasCustomFormatter(template) || hasPluralShorthand(template)) {
|
|
609
|
-
if (!
|
|
615
|
+
if (!cleanVars) {
|
|
610
616
|
const cfg = getConfig();
|
|
611
617
|
if (cfg.missingVarHandler) {
|
|
612
618
|
return interpolateICU(template, {}, resolvedLocale);
|
|
613
619
|
}
|
|
614
620
|
return template;
|
|
615
621
|
}
|
|
616
|
-
return interpolateICU(template,
|
|
622
|
+
return interpolateICU(template, cleanVars, resolvedLocale);
|
|
617
623
|
}
|
|
618
|
-
if (!
|
|
624
|
+
if (!cleanVars) {
|
|
619
625
|
const cfg = getConfig();
|
|
620
626
|
if (cfg.missingVarHandler) {
|
|
621
627
|
return template.replace(VARIABLE_PATTERN, (_, key) => {
|
|
@@ -625,7 +631,7 @@ function interpolate(template, vars, locale) {
|
|
|
625
631
|
return template;
|
|
626
632
|
}
|
|
627
633
|
return template.replace(VARIABLE_PATTERN, (_, key) => {
|
|
628
|
-
const value =
|
|
634
|
+
const value = cleanVars[key];
|
|
629
635
|
if (value !== void 0) return String(value);
|
|
630
636
|
const cfg = getConfig();
|
|
631
637
|
if (cfg.missingVarHandler) {
|
|
@@ -872,7 +878,18 @@ function getPluralCategory(count, locale) {
|
|
|
872
878
|
const rules = new Intl.PluralRules(locale);
|
|
873
879
|
return rules.select(count);
|
|
874
880
|
}
|
|
881
|
+
var CONTEXT_SEPARATOR = "#";
|
|
875
882
|
function findInDictionary(dict, key, vars, locale) {
|
|
883
|
+
if (vars?._context) {
|
|
884
|
+
const contextKey = `${key}${CONTEXT_SEPARATOR}${vars._context}`;
|
|
885
|
+
if (typeof vars.count === "number") {
|
|
886
|
+
const pluralKey = `${contextKey}_${getPluralCategory(vars.count, locale)}`;
|
|
887
|
+
const pluralTemplate = getNestedValue(dict, pluralKey);
|
|
888
|
+
if (pluralTemplate) return pluralTemplate;
|
|
889
|
+
}
|
|
890
|
+
const contextTemplate = getNestedValue(dict, contextKey);
|
|
891
|
+
if (contextTemplate !== void 0) return contextTemplate;
|
|
892
|
+
}
|
|
876
893
|
let template = getNestedValue(dict, key);
|
|
877
894
|
if (vars && typeof vars.count === "number") {
|
|
878
895
|
const pluralKey = `${key}_${getPluralCategory(vars.count, locale)}`;
|
|
@@ -936,12 +953,17 @@ function t(key, vars, locale) {
|
|
|
936
953
|
};
|
|
937
954
|
return applyDebugFormat(result, debugInfo);
|
|
938
955
|
}
|
|
939
|
-
function hasTranslation(key, locale) {
|
|
956
|
+
function hasTranslation(key, locale, context) {
|
|
940
957
|
const { namespace, key: actualKey } = parseKey(key);
|
|
941
958
|
const currentLocale2 = locale ?? getLocale();
|
|
942
959
|
const nsDictionaries = namespacedDictionaries[namespace] || {};
|
|
943
960
|
const dict = nsDictionaries[currentLocale2];
|
|
944
|
-
|
|
961
|
+
if (!dict) return false;
|
|
962
|
+
if (context) {
|
|
963
|
+
const contextKey = `${actualKey}${CONTEXT_SEPARATOR}${context}`;
|
|
964
|
+
return getNestedValue(dict, contextKey) !== void 0;
|
|
965
|
+
}
|
|
966
|
+
return getNestedValue(dict, actualKey) !== void 0;
|
|
945
967
|
}
|
|
946
968
|
function getLoadedLocales(namespace) {
|
|
947
969
|
if (namespace) {
|
|
@@ -1050,6 +1072,13 @@ function detectLocale(options) {
|
|
|
1050
1072
|
return defaultLocale;
|
|
1051
1073
|
}
|
|
1052
1074
|
|
|
1075
|
+
// src/scope.ts
|
|
1076
|
+
function createScope(namespace) {
|
|
1077
|
+
return (key, vars, locale) => {
|
|
1078
|
+
return t(`${namespace}:${key}`, vars, locale);
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1053
1082
|
// src/richtext.ts
|
|
1054
1083
|
function escapeRegExp(str) {
|
|
1055
1084
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -1091,6 +1120,7 @@ exports.clearDictionaries = clearDictionaries;
|
|
|
1091
1120
|
exports.clearFormatters = clearFormatters;
|
|
1092
1121
|
exports.clearICUCache = clearICUCache;
|
|
1093
1122
|
exports.configure = configure;
|
|
1123
|
+
exports.createScope = createScope;
|
|
1094
1124
|
exports.detectLocale = detectLocale;
|
|
1095
1125
|
exports.en_de = en_de;
|
|
1096
1126
|
exports.en_es = en_es;
|