@umituz/react-native-localization 2.3.0 → 2.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-localization",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "English-only localization system for React Native apps with i18n support",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * i18n Initializer
3
3
  *
4
- * Handles i18n configuration and initialization with namespace support
5
- * - Auto-discovers project translations
4
+ * Handles i18n configuration with namespace support
5
+ * - Loads package translations
6
+ * - Loads app translations (merges with package)
6
7
  * - Namespace-based organization (common, auth, etc.)
7
- * - React i18next integration
8
8
  */
9
9
 
10
10
  import i18n from 'i18next';
@@ -18,46 +18,55 @@ export class I18nInitializer {
18
18
  private static reactI18nextInitialized = false;
19
19
 
20
20
  /**
21
- * Build resources object with namespace support
21
+ * Build resources with package + app translations merged
22
22
  */
23
23
  private static buildResources(): Record<string, Record<string, any>> {
24
24
  const packageTranslations = TranslationLoader.loadPackageTranslations();
25
+ const appTranslations = TranslationLoader.loadAppTranslations();
25
26
 
26
- // Create namespace-based resources structure
27
27
  const resources: Record<string, Record<string, any>> = {
28
28
  'en-US': {},
29
29
  };
30
30
 
31
- // Package translations are already in namespace format (alerts, auth, etc.)
32
31
  const enUSPackage = packageTranslations['en-US'] || {};
33
32
 
34
- // Each key in packageTranslations is a namespace
33
+ // Add package namespaces
35
34
  for (const [namespace, translations] of Object.entries(enUSPackage)) {
36
35
  resources['en-US'][namespace] = translations;
37
36
  }
38
37
 
38
+ // Merge app namespaces (app overrides package)
39
+ for (const [namespace, translations] of Object.entries(appTranslations)) {
40
+ if (resources['en-US'][namespace]) {
41
+ resources['en-US'][namespace] = TranslationLoader.mergeTranslations(
42
+ resources['en-US'][namespace],
43
+ translations
44
+ );
45
+ } else {
46
+ resources['en-US'][namespace] = translations;
47
+ }
48
+ }
49
+
39
50
  return resources;
40
51
  }
41
52
 
42
- /**
43
- * Get all available namespaces from package translations
44
- */
45
53
  private static getNamespaces(): string[] {
46
54
  const packageTranslations = TranslationLoader.loadPackageTranslations();
55
+ const appTranslations = TranslationLoader.loadAppTranslations();
56
+
47
57
  const enUSPackage = packageTranslations['en-US'] || {};
48
- const namespaces = Object.keys(enUSPackage);
58
+ const namespaces = new Set([
59
+ ...Object.keys(enUSPackage),
60
+ ...Object.keys(appTranslations),
61
+ ]);
49
62
 
50
- // Ensure default namespace is included
51
- if (!namespaces.includes(DEFAULT_NAMESPACE)) {
52
- namespaces.unshift(DEFAULT_NAMESPACE);
63
+ if (!namespaces.has(DEFAULT_NAMESPACE)) {
64
+ namespaces.add(DEFAULT_NAMESPACE);
53
65
  }
54
66
 
55
- return namespaces;
67
+ return Array.from(namespaces);
56
68
  }
57
69
 
58
- /**
59
- * Initialize i18next with namespace support
60
- */
61
70
  static initialize(): void {
62
71
  if (i18n.isInitialized) {
63
72
  return;
@@ -79,37 +88,25 @@ export class I18nInitializer {
79
88
  ns: namespaces,
80
89
  defaultNS: DEFAULT_NAMESPACE,
81
90
  fallbackNS: DEFAULT_NAMESPACE,
82
-
83
- interpolation: {
84
- escapeValue: false,
85
- },
86
-
87
- react: {
88
- useSuspense: false,
89
- },
90
-
91
+ interpolation: { escapeValue: false },
92
+ react: { useSuspense: false },
91
93
  compatibilityJSON: 'v3',
92
94
  pluralSeparator: '_',
93
95
  keySeparator: '.',
94
96
  nsSeparator: ':',
95
-
96
97
  saveMissing: false,
97
98
  missingKeyHandler: false,
98
-
99
99
  debug: false,
100
100
  });
101
-
102
101
  } catch (error) {
103
102
  if (typeof __DEV__ !== 'undefined' && __DEV__) {
104
- console.error('[Localization] i18n initialization error:', error);
103
+ console.error('[Localization] init error:', error);
105
104
  }
106
105
  }
107
106
  }
108
107
 
109
108
  /**
110
- * Add additional translation resources with namespace support
111
- * @param languageCode - Language code (e.g., 'en-US')
112
- * @param namespaceResources - Object with namespace keys and translation objects
109
+ * Add translation resources at runtime
113
110
  */
114
111
  static addTranslationResources(
115
112
  languageCode: string,
@@ -1,58 +1,73 @@
1
1
  /**
2
2
  * Translation Loader
3
3
  *
4
- * Handles loading of translations from package only
4
+ * Loads translations from package and app
5
5
  */
6
6
 
7
7
  export class TranslationLoader {
8
8
  /**
9
- * Load package translations (en-US only)
9
+ * Load package translations (en-US)
10
10
  */
11
11
  static loadPackageTranslations(): Record<string, any> {
12
12
  try {
13
13
  // eslint-disable-next-line @typescript-eslint/no-require-imports
14
14
  const translations = require('../locales/en-US');
15
15
  return { 'en-US': translations.default || translations };
16
- } catch (error) {
16
+ } catch {
17
17
  return { 'en-US': {} };
18
18
  }
19
19
  }
20
20
 
21
21
  /**
22
- * Merge package defaults with project-specific translations
22
+ * Load app translations from common paths
23
23
  */
24
- static mergeTranslations(
25
- packageTranslations: any,
26
- projectTranslations: any
27
- ): any {
28
- if (!projectTranslations || Object.keys(projectTranslations).length === 0) {
29
- return packageTranslations;
24
+ static loadAppTranslations(): Record<string, any> {
25
+ const paths = [
26
+ '@/domains/localization/infrastructure/locales/en-US',
27
+ './src/domains/localization/infrastructure/locales/en-US',
28
+ '../../../src/domains/localization/infrastructure/locales/en-US',
29
+ ];
30
+
31
+ for (const path of paths) {
32
+ try {
33
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
34
+ const translations = require(path);
35
+ return translations.default || translations;
36
+ } catch {
37
+ continue;
38
+ }
39
+ }
40
+
41
+ return {};
42
+ }
43
+
44
+ /**
45
+ * Deep merge translations (app overrides package)
46
+ */
47
+ static mergeTranslations(base: any, override: any): any {
48
+ if (!override || Object.keys(override).length === 0) {
49
+ return base;
30
50
  }
31
51
 
32
- const merged = { ...packageTranslations };
33
-
34
- for (const key in projectTranslations) {
35
- if (projectTranslations.hasOwnProperty(key)) {
36
- if (
37
- typeof projectTranslations[key] === 'object' &&
38
- projectTranslations[key] !== null &&
39
- !Array.isArray(projectTranslations[key]) &&
40
- typeof packageTranslations[key] === 'object' &&
41
- packageTranslations[key] !== null &&
42
- !Array.isArray(packageTranslations[key])
43
- ) {
44
- // Deep merge nested objects
45
- merged[key] = this.mergeTranslations(
46
- packageTranslations[key],
47
- projectTranslations[key]
48
- );
52
+ const merged = { ...base };
53
+
54
+ for (const key in override) {
55
+ if (Object.prototype.hasOwnProperty.call(override, key)) {
56
+ const baseVal = base[key];
57
+ const overrideVal = override[key];
58
+
59
+ if (this.isObject(baseVal) && this.isObject(overrideVal)) {
60
+ merged[key] = this.mergeTranslations(baseVal, overrideVal);
49
61
  } else {
50
- // Override with project translation
51
- merged[key] = projectTranslations[key];
62
+ merged[key] = overrideVal;
52
63
  }
53
64
  }
54
65
  }
55
66
 
56
67
  return merged;
57
68
  }
69
+
70
+ private static isObject(val: any): boolean {
71
+ return val !== null && typeof val === 'object' && !Array.isArray(val);
72
+ }
58
73
  }