inline-i18n-multi 0.8.0 → 0.10.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
@@ -73,6 +73,12 @@ See "Hello" in your app? Just search for "Hello" in your codebase. **Done.**
73
73
  - **Translation Scope** - Namespace scoping with `createScope` (`createScope('common')` returns a scoped `t()`)
74
74
  - **Unused Key Detection** - CLI `--unused` flag to detect unused translation keys
75
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
79
+ - **Fallback Value** - Custom fallback text when translation is missing (`t('key', { _fallback: 'Default' })`)
80
+ - **Diff Command** - Compare translations between two locales (`npx inline-i18n diff en ko`)
81
+ - **Stats Command** - Translation statistics dashboard (`npx inline-i18n stats`)
76
82
 
77
83
  ---
78
84
 
@@ -701,6 +707,113 @@ The generated types enable autocomplete and type checking for `t()` function key
701
707
 
702
708
  ---
703
709
 
710
+ ## Context System
711
+
712
+ Use the `_context` parameter to disambiguate translations for the same key based on context. Dictionary keys use the `key#context` format:
713
+
714
+ ```typescript
715
+ import { t, loadDictionaries } from 'inline-i18n-multi'
716
+
717
+ loadDictionaries({
718
+ en: {
719
+ greeting: 'Hi',
720
+ 'greeting#formal': 'Good day',
721
+ 'greeting#casual': 'Hey',
722
+ },
723
+ ko: {
724
+ greeting: '안녕하세요',
725
+ 'greeting#formal': '안녕하십니까',
726
+ 'greeting#casual': '야',
727
+ }
728
+ })
729
+
730
+ // No context (default)
731
+ t('greeting') // → "Hi"
732
+
733
+ // Formal context
734
+ t('greeting', { _context: 'formal' }) // → "Good day"
735
+
736
+ // Casual context
737
+ t('greeting', { _context: 'casual' }) // → "Hey"
738
+
739
+ // Falls back to the uncontexted key when a contexted key is not found
740
+ t('greeting', { _context: 'unknown' }) // → "Hi"
741
+ ```
742
+
743
+ Useful when the same translation key has different meanings depending on context (e.g., "open" used as both a verb and an adjective).
744
+
745
+ ---
746
+
747
+ ## Translation Extraction
748
+
749
+ Extract inline translations from source code into JSON files using the `extract` command:
750
+
751
+ ```bash
752
+ # Default (outputs to ./locales directory)
753
+ npx inline-i18n extract
754
+
755
+ # Specify output directory
756
+ npx inline-i18n extract --output ./src/locales
757
+
758
+ # Specify locales
759
+ npx inline-i18n extract --locales en,ko,ja
760
+ ```
761
+
762
+ Scans `it()` calls in your source code and generates per-locale JSON files. Existing JSON files are preserved, with only new keys being added.
763
+
764
+ ---
765
+
766
+ ## CLI Watch Mode
767
+
768
+ Add the `--watch` flag to `validate` and `typegen` commands to automatically re-run on file changes:
769
+
770
+ ```bash
771
+ # Watch mode for validation
772
+ npx inline-i18n validate --watch
773
+
774
+ # Watch mode for type generation
775
+ npx inline-i18n typegen --output src/i18n.d.ts --watch
776
+
777
+ # Combine with strict mode
778
+ npx inline-i18n validate --strict --watch
779
+ ```
780
+
781
+ Provides instant feedback during development by re-running validation or type generation each time a file is saved.
782
+
783
+ ---
784
+
785
+ ## Fallback Value
786
+
787
+ Provide custom fallback text when a translation key is missing, instead of returning the raw key:
788
+
789
+ ```typescript
790
+ import { t, loadDictionaries, setLocale } from 'inline-i18n-multi'
791
+
792
+ loadDictionaries({
793
+ en: { greeting: 'Hello' }
794
+ })
795
+
796
+ setLocale('en')
797
+
798
+ // Without _fallback: returns the raw key when missing
799
+ t('missing.key') // → "missing.key"
800
+
801
+ // With _fallback: returns custom fallback text
802
+ t('missing.key', { _fallback: 'Default text' }) // → "Default text"
803
+
804
+ // _fallback is stripped from interpolation output (not passed as a variable)
805
+ t('greeting', { _fallback: 'Fallback' }) // → "Hello" (uses real translation, ignores _fallback)
806
+
807
+ // Works with variables — _fallback is not treated as a variable
808
+ t('welcome', { name: 'John', _fallback: 'Welcome!' })
809
+ // If 'welcome' exists: uses translation with {name} interpolated
810
+ // If 'welcome' is missing: → "Welcome!"
811
+ ```
812
+
813
+ Useful for providing user-friendly defaults in UI components where raw keys would be confusing.
814
+
815
+ ---
816
+
704
817
  ## Configuration
705
818
 
706
819
  Configure global settings for fallback behavior and warnings:
@@ -944,7 +1057,7 @@ npm install inline-i18n-multi-next
944
1057
 
945
1058
  ### CLI
946
1059
 
947
- 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`.
1060
+ 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`. Compare locales with `inline-i18n diff en ko` and view statistics with `inline-i18n stats`. Supports `--watch` mode for `validate` and `typegen`.
948
1061
 
949
1062
  ```bash
950
1063
  npm install -D @inline-i18n-multi/cli
package/dist/index.d.mts CHANGED
@@ -10,8 +10,13 @@ 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)
14
+ * Use _fallback for custom fallback text when key is missing (v0.10.0)
13
15
  */
14
- type TranslationVars = Record<string, string | number | Date | string[]>;
16
+ type TranslationVars = Record<string, string | number | Date | string[]> & {
17
+ _context?: string;
18
+ _fallback?: string;
19
+ };
15
20
  /**
16
21
  * Warning information for missing translations
17
22
  */
@@ -210,8 +215,9 @@ declare function t(key: string, vars?: TranslationVars, locale?: Locale): string
210
215
  * Check if a translation key exists
211
216
  * @param key - Translation key (may include namespace prefix)
212
217
  * @param locale - Optional locale to check
218
+ * @param context - Optional context for contextual translations (v0.9.0)
213
219
  */
214
- declare function hasTranslation(key: string, locale?: Locale): boolean;
220
+ declare function hasTranslation(key: string, locale?: Locale, context?: string): boolean;
215
221
  /**
216
222
  * Get all loaded locales
217
223
  * @param namespace - Optional namespace (returns from all if not specified)
package/dist/index.d.ts CHANGED
@@ -10,8 +10,13 @@ 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)
14
+ * Use _fallback for custom fallback text when key is missing (v0.10.0)
13
15
  */
14
- type TranslationVars = Record<string, string | number | Date | string[]>;
16
+ type TranslationVars = Record<string, string | number | Date | string[]> & {
17
+ _context?: string;
18
+ _fallback?: string;
19
+ };
15
20
  /**
16
21
  * Warning information for missing translations
17
22
  */
@@ -210,8 +215,9 @@ declare function t(key: string, vars?: TranslationVars, locale?: Locale): string
210
215
  * Check if a translation key exists
211
216
  * @param key - Translation key (may include namespace prefix)
212
217
  * @param locale - Optional locale to check
218
+ * @param context - Optional context for contextual translations (v0.9.0)
213
219
  */
214
- declare function hasTranslation(key: string, locale?: Locale): boolean;
220
+ declare function hasTranslation(key: string, locale?: Locale, context?: string): boolean;
215
221
  /**
216
222
  * Get all loaded locales
217
223
  * @param namespace - Optional namespace (returns from all if not specified)
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 stripSpecialVars(vars) {
607
+ if (!("_context" in vars) && !("_fallback" in vars)) return vars;
608
+ const { _context: _c, _fallback: _f, ...rest } = vars;
609
+ return rest;
610
+ }
606
611
  function interpolate(template, vars, locale) {
607
612
  const resolvedLocale = locale || "en";
613
+ const cleanVars = vars ? stripSpecialVars(vars) : vars;
608
614
  if (hasICUPattern(template) || hasCustomFormatter(template) || hasPluralShorthand(template)) {
609
- if (!vars) {
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, vars, resolvedLocale);
622
+ return interpolateICU(template, cleanVars, resolvedLocale);
617
623
  }
618
- if (!vars) {
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 = vars[key];
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)}`;
@@ -914,6 +931,9 @@ function t(key, vars, locale) {
914
931
  requestedLocale: currentLocale2,
915
932
  key
916
933
  };
934
+ if (vars?._fallback !== void 0) {
935
+ return applyDebugFormat(vars._fallback, debugInfo2);
936
+ }
917
937
  return applyDebugFormat(key, debugInfo2);
918
938
  }
919
939
  const isFallback = usedLocale !== currentLocale2;
@@ -936,12 +956,17 @@ function t(key, vars, locale) {
936
956
  };
937
957
  return applyDebugFormat(result, debugInfo);
938
958
  }
939
- function hasTranslation(key, locale) {
959
+ function hasTranslation(key, locale, context) {
940
960
  const { namespace, key: actualKey } = parseKey(key);
941
961
  const currentLocale2 = locale ?? getLocale();
942
962
  const nsDictionaries = namespacedDictionaries[namespace] || {};
943
963
  const dict = nsDictionaries[currentLocale2];
944
- return dict ? getNestedValue(dict, actualKey) !== void 0 : false;
964
+ if (!dict) return false;
965
+ if (context) {
966
+ const contextKey = `${actualKey}${CONTEXT_SEPARATOR}${context}`;
967
+ return getNestedValue(dict, contextKey) !== void 0;
968
+ }
969
+ return getNestedValue(dict, actualKey) !== void 0;
945
970
  }
946
971
  function getLoadedLocales(namespace) {
947
972
  if (namespace) {