@umituz/react-native-localization 2.2.2 → 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.2.2",
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,117 +1,120 @@
1
1
  /**
2
2
  * i18n Initializer
3
3
  *
4
- * Handles i18n configuration and initialization
5
- * - Auto-discovers project translations
6
- * - i18n setup
7
- * - React i18next integration
4
+ * Handles i18n configuration with namespace support
5
+ * - Loads package translations
6
+ * - Loads app translations (merges with package)
7
+ * - Namespace-based organization (common, auth, etc.)
8
8
  */
9
9
 
10
10
  import i18n from 'i18next';
11
11
  import { initReactI18next } from 'react-i18next';
12
- import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from './languages';
12
+ import { DEFAULT_LANGUAGE } from './languages';
13
13
  import { TranslationLoader } from './TranslationLoader';
14
14
 
15
+ const DEFAULT_NAMESPACE = 'common';
16
+
15
17
  export class I18nInitializer {
16
18
  private static reactI18nextInitialized = false;
17
19
 
18
20
  /**
19
- * Auto-discover project translations from common paths
21
+ * Build resources with package + app translations merged
20
22
  */
21
- private static loadProjectTranslations(): Record<string, any> {
22
- const possiblePaths = [
23
- './src/locales/en-US', // App structure
24
- './locales/en-US', // Alternative app structure
25
- '../src/locales/en-US', // Relative from package
26
- ];
27
-
28
- for (const path of possiblePaths) {
29
- try {
30
- // eslint-disable-next-line @typescript-eslint/no-require-imports
31
- const translations = require(path);
32
- return translations.default || translations;
33
- } catch {
34
- // Try next path
23
+ private static buildResources(): Record<string, Record<string, any>> {
24
+ const packageTranslations = TranslationLoader.loadPackageTranslations();
25
+ const appTranslations = TranslationLoader.loadAppTranslations();
26
+
27
+ const resources: Record<string, Record<string, any>> = {
28
+ 'en-US': {},
29
+ };
30
+
31
+ const enUSPackage = packageTranslations['en-US'] || {};
32
+
33
+ // Add package namespaces
34
+ for (const [namespace, translations] of Object.entries(enUSPackage)) {
35
+ resources['en-US'][namespace] = translations;
36
+ }
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;
35
47
  }
36
48
  }
37
49
 
38
- return {};
50
+ return resources;
39
51
  }
40
52
 
41
- /**
42
- * Build resources object for all supported languages
43
- */
44
- private static buildResources(): Record<string, { translation: any }> {
45
- const resources: Record<string, { translation: any }> = {};
53
+ private static getNamespaces(): string[] {
46
54
  const packageTranslations = TranslationLoader.loadPackageTranslations();
47
- const projectTranslations = this.loadProjectTranslations();
48
-
49
- // For en-US, merge package and project translations
50
- resources['en-US'] = {
51
- translation: TranslationLoader.mergeTranslations(
52
- packageTranslations['en-US'] || {},
53
- projectTranslations
54
- ),
55
- };
55
+ const appTranslations = TranslationLoader.loadAppTranslations();
56
56
 
57
- return resources;
57
+ const enUSPackage = packageTranslations['en-US'] || {};
58
+ const namespaces = new Set([
59
+ ...Object.keys(enUSPackage),
60
+ ...Object.keys(appTranslations),
61
+ ]);
62
+
63
+ if (!namespaces.has(DEFAULT_NAMESPACE)) {
64
+ namespaces.add(DEFAULT_NAMESPACE);
65
+ }
66
+
67
+ return Array.from(namespaces);
58
68
  }
59
69
 
60
- /**
61
- * Initialize i18next
62
- */
63
70
  static initialize(): void {
64
- // Prevent multiple initializations
65
71
  if (i18n.isInitialized) {
66
72
  return;
67
73
  }
68
74
 
69
75
  try {
70
- // Use initReactI18next once
71
76
  if (!this.reactI18nextInitialized) {
72
77
  i18n.use(initReactI18next);
73
78
  this.reactI18nextInitialized = true;
74
79
  }
75
80
 
76
81
  const resources = this.buildResources();
82
+ const namespaces = this.getNamespaces();
77
83
 
78
84
  i18n.init({
79
85
  resources,
80
86
  lng: DEFAULT_LANGUAGE,
81
87
  fallbackLng: DEFAULT_LANGUAGE,
82
-
83
- interpolation: {
84
- escapeValue: false, // React already escapes values
85
- },
86
-
87
- react: {
88
- useSuspense: false, // Disable suspense for React Native
89
- },
90
-
91
- compatibilityJSON: 'v3', // Use v3 format for React Native
92
- pluralSeparator: '_', // Use underscore separator for plural keys
93
- keySeparator: '.', // Use dot separator for nested keys
94
-
95
- debug: typeof __DEV__ !== 'undefined' && __DEV__,
88
+ ns: namespaces,
89
+ defaultNS: DEFAULT_NAMESPACE,
90
+ fallbackNS: DEFAULT_NAMESPACE,
91
+ interpolation: { escapeValue: false },
92
+ react: { useSuspense: false },
93
+ compatibilityJSON: 'v3',
94
+ pluralSeparator: '_',
95
+ keySeparator: '.',
96
+ nsSeparator: ':',
97
+ saveMissing: false,
98
+ missingKeyHandler: false,
99
+ debug: false,
96
100
  });
97
-
98
101
  } catch (error) {
99
- // Don't throw - allow app to continue without i18n
100
102
  if (typeof __DEV__ !== 'undefined' && __DEV__) {
101
- console.error(' i18n initialization error:', error);
103
+ console.error('[Localization] init error:', error);
102
104
  }
103
105
  }
104
106
  }
105
107
 
106
108
  /**
107
- * Add additional translation resources
109
+ * Add translation resources at runtime
108
110
  */
109
- static addTranslationResources(resources: Record<string, { translation: any }>): void {
110
- for (const [langCode, resource] of Object.entries(resources)) {
111
- if (resource.translation) {
112
- const existingTranslations = i18n.getResourceBundle(langCode, 'translation') || {};
113
- const mergedTranslations = { ...existingTranslations, ...resource.translation };
114
- i18n.addResourceBundle(langCode, 'translation', mergedTranslations, true, true);
111
+ static addTranslationResources(
112
+ languageCode: string,
113
+ namespaceResources: Record<string, any>
114
+ ): void {
115
+ for (const [namespace, translations] of Object.entries(namespaceResources)) {
116
+ if (translations && typeof translations === 'object') {
117
+ i18n.addResourceBundle(languageCode, namespace, translations, true, true);
115
118
  }
116
119
  }
117
120
  }
@@ -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
  }
@@ -1,15 +1,14 @@
1
1
  /**
2
2
  * i18n Configuration
3
3
  *
4
- * Auto-initializes i18n with project translations
4
+ * Auto-initializes i18n with namespace support
5
+ * Usage: t('namespace:key') e.g., t('common:cancel')
5
6
  */
6
7
 
7
8
  import { I18nInitializer } from './I18nInitializer';
8
9
  import i18n from 'i18next';
9
10
 
10
- // Initialize i18n automatically
11
11
  I18nInitializer.initialize();
12
12
 
13
- // Export for advanced usage
14
13
  export const addTranslationResources = I18nInitializer.addTranslationResources;
15
14
  export default i18n;
@@ -14,55 +14,6 @@ import i18n from '../config/i18n';
14
14
  * Hook for translation functionality
15
15
  */
16
16
  export const useTranslationFunction = (): ((key: string, options?: any) => string) => {
17
- // Ensure settings translations are loaded
18
- if (i18n.isInitialized && typeof i18n.t === 'function') {
19
- const existingTranslations = i18n.getResourceBundle('en-US', 'translation') || {};
20
-
21
- // Check if settings translations are missing
22
- if (!existingTranslations.settings) {
23
- // Add settings translations as fallback
24
- const settingsTranslations = {
25
- settings: {
26
- editProfile: "Edit Profile",
27
- sections: {
28
- physicalInfoAndGoals: "Physical Info and Goals",
29
- appSettings: "App Settings",
30
- accountManagement: "Account Management"
31
- },
32
- personalInfo: {
33
- title: "Personal Information",
34
- subtitle: "Height, Weight, Age, Gender"
35
- },
36
- nutritionGoals: {
37
- title: "Nutrition Goals",
38
- subtitle: "Calories, Protein, Carbs, Fat"
39
- },
40
- notifications: {
41
- title: "Notification Preferences",
42
- subtitle: "Water and meal reminders"
43
- },
44
- darkMode: {
45
- title: "Dark Mode",
46
- subtitle: "Change app theme"
47
- },
48
- passwordChange: {
49
- title: "Change Password"
50
- },
51
- saveChanges: "Save Changes",
52
- logout: "Logout",
53
- emptyState: {
54
- title: "No recipes yet",
55
- subtitle: "Create your first recipe to get started"
56
- },
57
- addToList: "Add to List"
58
- }
59
- };
60
-
61
- const mergedTranslations = { ...existingTranslations, ...settingsTranslations };
62
- i18n.addResourceBundle('en-US', 'translation', mergedTranslations, true, true);
63
- }
64
- }
65
-
66
17
  // Use direct i18n.t for reliability (no React context issues)
67
18
  return (key: string, options?: any): string => {
68
19
  if (i18n.isInitialized && typeof i18n.t === 'function') {
@@ -0,0 +1,57 @@
1
+ {
2
+ "cancel": "Cancel",
3
+ "confirm": "Confirm",
4
+ "continue": "Continue",
5
+ "delete": "Delete",
6
+ "edit": "Edit",
7
+ "error": "Error",
8
+ "loading": "Loading...",
9
+ "notNow": "Not Now",
10
+ "ok": "OK",
11
+ "optional": "Optional",
12
+ "save": "Save",
13
+ "somethingWentWrong": "Something went wrong",
14
+ "success": "Success",
15
+ "close": "Close",
16
+ "done": "Done",
17
+ "next": "Next",
18
+ "previous": "Previous",
19
+ "skip": "Skip",
20
+ "retry": "Retry",
21
+ "refresh": "Refresh",
22
+ "filter": "Filter",
23
+ "sort": "Sort",
24
+ "add": "Add",
25
+ "remove": "Remove",
26
+ "update": "Update",
27
+ "create": "Create",
28
+ "view": "View",
29
+ "details": "Details",
30
+ "share": "Share",
31
+ "download": "Download",
32
+ "upload": "Upload",
33
+ "submit": "Submit",
34
+ "reset": "Reset",
35
+ "clear": "Clear",
36
+ "apply": "Apply",
37
+ "yes": "Yes",
38
+ "no": "No",
39
+ "or": "or",
40
+ "all": "All",
41
+ "none": "None",
42
+ "of": "of",
43
+ "select": "Select",
44
+ "selected": "Selected",
45
+ "required": "Required",
46
+ "empty": "Empty",
47
+ "noData": "No data available",
48
+ "noResults": "No results found",
49
+ "tryAgain": "Try again",
50
+ "learnMore": "Learn more",
51
+ "getStarted": "Get started",
52
+ "viewAll": "View all",
53
+ "showMore": "Show more",
54
+ "showLess": "Show less",
55
+ "back": "Back",
56
+ "start": "Start"
57
+ }
@@ -1,16 +1,19 @@
1
1
  /**
2
- * en-US Translation Module
2
+ * Translation loader for en-US with namespace support
3
3
  *
4
- * Direct loading for maximum compatibility across platforms
5
- * - Explicit imports for reliable bundling
6
- * - Flattened with dot notation
7
- * - Production-ready and tested
4
+ * Each JSON file represents a namespace that can be accessed via:
5
+ * t('namespace:key') or t('namespace:nested.key')
6
+ *
7
+ * Example:
8
+ * t('common:cancel') -> "Cancel"
9
+ * t('auth:login.title') -> "Sign In"
8
10
  */
9
11
 
10
12
  import alerts from './alerts.json';
11
13
  import auth from './auth.json';
12
14
  import branding from './branding.json';
13
15
  import clipboard from './clipboard.json';
16
+ import common from './common.json';
14
17
  import datetime from './datetime.json';
15
18
  import device from './device.json';
16
19
  import editor from './editor.json';
@@ -26,48 +29,26 @@ import settings from './settings.json';
26
29
  import sharing from './sharing.json';
27
30
  import templates from './templates.json';
28
31
 
29
- /**
30
- * Flatten nested objects with dot notation
31
- */
32
- const flattenObject = (
33
- obj: Record<string, any>,
34
- prefix = '',
35
- ): Record<string, string> => {
36
- const flattened: Record<string, string> = {};
37
-
38
- Object.keys(obj).forEach((key) => {
39
- const newKey = prefix ? `${prefix}.${key}` : key;
40
-
41
- if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
42
- Object.assign(flattened, flattenObject(obj[key], newKey));
43
- } else {
44
- flattened[newKey] = obj[key];
45
- }
46
- });
47
-
48
- return flattened;
49
- };
50
-
51
- // Create flattened translations object
52
32
  const translations = {
53
- ...flattenObject(alerts, 'alerts'),
54
- ...flattenObject(auth, 'auth'),
55
- ...flattenObject(branding, 'branding'),
56
- ...flattenObject(clipboard, 'clipboard'),
57
- ...flattenObject(datetime, 'datetime'),
58
- ...flattenObject(device, 'device'),
59
- ...flattenObject(editor, 'editor'),
60
- ...flattenObject(errors, 'errors'),
61
- ...flattenObject(general, 'general'),
62
- ...flattenObject(goals, 'goals'),
63
- ...flattenObject(haptics, 'haptics'),
64
- ...flattenObject(home, 'home'),
65
- ...flattenObject(navigation, 'navigation'),
66
- ...flattenObject(onboarding, 'onboarding'),
67
- ...flattenObject(projects, 'projects'),
68
- ...flattenObject(settings, 'settings'),
69
- ...flattenObject(sharing, 'sharing'),
70
- ...flattenObject(templates, 'templates'),
33
+ alerts,
34
+ auth,
35
+ branding,
36
+ clipboard,
37
+ common,
38
+ datetime,
39
+ device,
40
+ editor,
41
+ errors,
42
+ general,
43
+ goals,
44
+ haptics,
45
+ home,
46
+ navigation,
47
+ onboarding,
48
+ projects,
49
+ settings,
50
+ sharing,
51
+ templates,
71
52
  };
72
53
 
73
54
  export default translations;
@@ -1,9 +1,9 @@
1
1
  /**
2
- * AsyncStorage Wrapper
3
- * Simple wrapper for AsyncStorage operations
2
+ * Storage Wrapper
3
+ * Uses @umituz/react-native-storage for persistence
4
4
  */
5
5
 
6
- import AsyncStorage from '@react-native-async-storage/async-storage';
6
+ import { storageRepository } from '@umituz/react-native-storage';
7
7
 
8
8
  export const STORAGE_KEYS = {
9
9
  LANGUAGE: '@localization:language',
@@ -11,19 +11,14 @@ export const STORAGE_KEYS = {
11
11
 
12
12
  export const StorageWrapper = {
13
13
  async getString(key: string, defaultValue: string): Promise<string> {
14
- try {
15
- const value = await AsyncStorage.getItem(key);
16
- return value ?? defaultValue;
17
- } catch (error) {
18
- return defaultValue;
14
+ const result = await storageRepository.getString(key, defaultValue);
15
+ if (result.success && result.data !== null) {
16
+ return result.data;
19
17
  }
18
+ return defaultValue;
20
19
  },
21
20
 
22
21
  async setString(key: string, value: string): Promise<void> {
23
- try {
24
- await AsyncStorage.setItem(key, value);
25
- } catch (error) {
26
- // Ignore storage errors
27
- }
22
+ await storageRepository.setString(key, value);
28
23
  },
29
24
  };
@@ -7,58 +7,42 @@
7
7
  * - i18n setup
8
8
  */
9
9
 
10
- import AsyncStorage from '@react-native-async-storage/async-storage';
10
+ import { storageRepository } from '@umituz/react-native-storage';
11
11
  import i18n from '../config/i18n';
12
12
  import { DEFAULT_LANGUAGE, getLanguageByCode, getDeviceLocale } from '../config/languages';
13
13
 
14
- // Storage key for language preference
15
14
  const LANGUAGE_STORAGE_KEY = '@localization:language';
16
15
 
17
16
  export class LanguageInitializer {
18
17
  /**
19
18
  * Initialize localization system
20
- * Detects device locale and sets up i18n
21
19
  */
22
20
  static async initialize(): Promise<{
23
21
  languageCode: string;
24
22
  isRTL: boolean;
25
23
  }> {
26
24
  try {
27
- // Get saved language preference
28
- const savedLanguage = await AsyncStorage.getItem(LANGUAGE_STORAGE_KEY) || DEFAULT_LANGUAGE;
29
-
30
- // Determine language code
25
+ const savedResult = await storageRepository.getString(LANGUAGE_STORAGE_KEY, DEFAULT_LANGUAGE);
26
+ const savedLanguage = savedResult.success && savedResult.data ? savedResult.data : DEFAULT_LANGUAGE;
31
27
  const languageCode = await this.determineLanguageCode(savedLanguage);
32
-
33
- // Validate and get language object
34
28
  const finalLanguage = await this.validateAndSetupLanguage(languageCode);
35
29
 
36
30
  return finalLanguage;
37
31
  } catch (error) {
38
- // Fallback to default language
39
32
  return await this.setupFallbackLanguage();
40
33
  }
41
34
  }
42
35
 
43
- /**
44
- * Determine which language code to use
45
- */
46
36
  private static async determineLanguageCode(savedLanguage: string): Promise<string> {
47
37
  if (savedLanguage && savedLanguage !== DEFAULT_LANGUAGE) {
48
- // User has previously selected a language
49
38
  return savedLanguage;
50
- } else {
51
- // First launch - detect device locale
52
- const deviceLocale = getDeviceLocale();
53
- // Save detected locale for future launches
54
- await AsyncStorage.setItem(LANGUAGE_STORAGE_KEY, deviceLocale);
55
- return deviceLocale;
56
39
  }
40
+
41
+ const deviceLocale = getDeviceLocale();
42
+ await storageRepository.setString(LANGUAGE_STORAGE_KEY, deviceLocale);
43
+ return deviceLocale;
57
44
  }
58
45
 
59
- /**
60
- * Validate language and set it up in i18n
61
- */
62
46
  private static async validateAndSetupLanguage(languageCode: string): Promise<{
63
47
  languageCode: string;
64
48
  isRTL: boolean;
@@ -75,9 +59,6 @@ export class LanguageInitializer {
75
59
  };
76
60
  }
77
61
 
78
- /**
79
- * Set up fallback language when initialization fails
80
- */
81
62
  private static async setupFallbackLanguage(): Promise<{
82
63
  languageCode: string;
83
64
  isRTL: boolean;
@@ -3,15 +3,13 @@
3
3
  *
4
4
  * Handles switching between languages
5
5
  * - Language validation
6
- * - Dynamic resource loading
7
6
  * - Persistence
8
7
  */
9
8
 
10
- import AsyncStorage from '@react-native-async-storage/async-storage';
9
+ import { storageRepository } from '@umituz/react-native-storage';
11
10
  import i18n from '../config/i18n';
12
11
  import { getLanguageByCode } from '../config/languages';
13
12
 
14
- // Storage key for language preference
15
13
  const LANGUAGE_STORAGE_KEY = '@localization:language';
16
14
 
17
15
  export class LanguageSwitcher {
@@ -24,60 +22,16 @@ export class LanguageSwitcher {
24
22
  }> {
25
23
  const language = getLanguageByCode(languageCode);
26
24
 
27
- // Validate language exists
28
25
  if (!language) {
29
26
  throw new Error(`Unsupported language: ${languageCode}`);
30
27
  }
31
28
 
32
- // Load language resources if needed
33
- await this.loadLanguageResources(languageCode);
34
-
35
- // Update i18n
36
29
  await i18n.changeLanguage(languageCode);
37
-
38
- // Persist language preference
39
- await AsyncStorage.setItem(LANGUAGE_STORAGE_KEY, languageCode);
30
+ await storageRepository.setString(LANGUAGE_STORAGE_KEY, languageCode);
40
31
 
41
32
  return {
42
33
  languageCode,
43
34
  isRTL: language.rtl || false,
44
35
  };
45
36
  }
46
-
47
- /**
48
- * Load language resources dynamically
49
- */
50
- private static async loadLanguageResources(languageCode: string): Promise<void> {
51
- if (i18n.hasResourceBundle(languageCode, 'translation')) {
52
- return; // Already loaded
53
- }
54
-
55
- try {
56
- // Try to load project translations from common paths
57
- let translations: any = null;
58
-
59
- const loadPaths = [
60
- `../../../../../../src/domains/localization/infrastructure/locales/${languageCode}`,
61
- `../../../../../../domains/localization/infrastructure/locales/${languageCode}`,
62
- `../../../../../../src/locales/${languageCode}`,
63
- ];
64
-
65
- for (const path of loadPaths) {
66
- try {
67
- // eslint-disable-next-line @typescript-eslint/no-require-imports
68
- translations = require(path);
69
- break;
70
- } catch {
71
- // Try next path
72
- }
73
- }
74
-
75
- if (translations) {
76
- const translationData = translations.default || translations;
77
- i18n.addResourceBundle(languageCode, 'translation', translationData, true, true);
78
- }
79
- } catch (loadError) {
80
- // If loading fails, continue with changeLanguage (will fallback to en-US)
81
- }
82
- }
83
37
  }