@kesbyte/capacitor-exif-gallery 1.0.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.
Files changed (40) hide show
  1. package/CapacitorExifGallery.podspec +17 -0
  2. package/LICENSE +8 -0
  3. package/Package.swift +28 -0
  4. package/README.md +813 -0
  5. package/dist/esm/ExifGalleryImpl.d.ts +176 -0
  6. package/dist/esm/ExifGalleryImpl.js +295 -0
  7. package/dist/esm/ExifGalleryImpl.js.map +1 -0
  8. package/dist/esm/FilterValidator.d.ts +126 -0
  9. package/dist/esm/FilterValidator.js +274 -0
  10. package/dist/esm/FilterValidator.js.map +1 -0
  11. package/dist/esm/PluginState.d.ts +128 -0
  12. package/dist/esm/PluginState.js +166 -0
  13. package/dist/esm/PluginState.js.map +1 -0
  14. package/dist/esm/PolylineDecoder.d.ts +23 -0
  15. package/dist/esm/PolylineDecoder.js +34 -0
  16. package/dist/esm/PolylineDecoder.js.map +1 -0
  17. package/dist/esm/TranslationLoader.d.ts +140 -0
  18. package/dist/esm/TranslationLoader.js +218 -0
  19. package/dist/esm/TranslationLoader.js.map +1 -0
  20. package/dist/esm/definitions.d.ts +539 -0
  21. package/dist/esm/definitions.js +2 -0
  22. package/dist/esm/definitions.js.map +1 -0
  23. package/dist/esm/errors.d.ts +252 -0
  24. package/dist/esm/errors.js +276 -0
  25. package/dist/esm/errors.js.map +1 -0
  26. package/dist/esm/index.d.ts +40 -0
  27. package/dist/esm/index.js +42 -0
  28. package/dist/esm/index.js.map +1 -0
  29. package/dist/esm/translations/de.json +20 -0
  30. package/dist/esm/translations/en.json +20 -0
  31. package/dist/esm/translations/es.json +20 -0
  32. package/dist/esm/translations/fr.json +20 -0
  33. package/dist/esm/web.d.ts +6 -0
  34. package/dist/esm/web.js +14 -0
  35. package/dist/esm/web.js.map +1 -0
  36. package/dist/plugin.cjs.js +1820 -0
  37. package/dist/plugin.cjs.js.map +1 -0
  38. package/dist/plugin.js +1823 -0
  39. package/dist/plugin.js.map +1 -0
  40. package/package.json +121 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PolylineDecoder.js","sourceRoot":"","sources":["../../src/PolylineDecoder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAI7C;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,eAAe;IAC1B;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,OAAe;QAC3B,IAAI,CAAC;YACH,gEAAgE;YAChE,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAEzC,4BAA4B;YAC5B,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QACrD,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,8BAA8B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;CACF","sourcesContent":["import * as polyline from '@mapbox/polyline';\n\nimport type { LatLng } from './definitions';\n\n/**\n * Decodes Google's Encoded Polyline format to coordinate array.\n *\n * Algorithm: https://developers.google.com/maps/documentation/utilities/polylinealgorithm\n *\n * Precision: Uses precision 5 (Google Maps default, ±1 meter accuracy).\n * Higher precision polylines (precision 6+) are also supported automatically.\n *\n * @param encoded - Encoded polyline string (e.g., \"_p~iF~ps|U_ulLnnqC\")\n * @returns Array of LatLng coordinates\n * @throws Error if encoded string is malformed or exceeds limits\n *\n * @example\n * const coords = PolylineDecoder.decode(\"_p~iF~ps|U_ulLnnqC\");\n * // Returns: [{ lat: 38.5, lng: -120.2 }, { lat: 40.7, lng: -120.95 }, ...]\n */\nexport class PolylineDecoder {\n /**\n * Decodes an encoded polyline string to LatLng coordinate array.\n */\n static decode(encoded: string): LatLng[] {\n try {\n // polyline package returns array of [lat, lng] coordinate pairs\n const decoded = polyline.decode(encoded);\n\n // Convert to LatLng objects\n return decoded.map(([lat, lng]) => ({ lat, lng }));\n } catch (error: any) {\n throw new Error(`Failed to decode polyline: ${error.message}`);\n }\n }\n}\n"]}
@@ -0,0 +1,140 @@
1
+ import type { SupportedLocale, TranslationSet } from './definitions';
2
+ /**
3
+ * Translation loader utility for language detection and merging.
4
+ *
5
+ * Handles:
6
+ * - System language detection via navigator.language
7
+ * - Default translation loading
8
+ * - Custom translation merging
9
+ * - Validation of locale and custom keys
10
+ *
11
+ * @internal
12
+ */
13
+ export declare class TranslationLoader {
14
+ /**
15
+ * All required keys in a valid TranslationSet.
16
+ * Used for validation of custom overrides.
17
+ */
18
+ private static readonly REQUIRED_KEYS;
19
+ /**
20
+ * Default translations by locale.
21
+ */
22
+ private static readonly DEFAULT_TRANSLATIONS;
23
+ /**
24
+ * Detect system language from navigator.language.
25
+ *
26
+ * Logic:
27
+ * 1. Get navigator.language (e.g., 'de-DE', 'fr-FR', 'en-US')
28
+ * 2. Extract language code (first 2 chars: 'de', 'fr', 'en')
29
+ * 3. Check if supported ('en' | 'de' | 'fr' | 'es')
30
+ * 4. Fallback to 'en' if not supported
31
+ *
32
+ * @returns Detected SupportedLocale, fallback to 'en'
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * // navigator.language = 'de-DE'
37
+ * detectSystemLanguage(); // 'de'
38
+ *
39
+ * // navigator.language = 'ja-JP' (not supported)
40
+ * detectSystemLanguage(); // 'en' (fallback)
41
+ * ```
42
+ */
43
+ static detectSystemLanguage(): Promise<SupportedLocale>;
44
+ /**
45
+ * Load default translations for a given locale.
46
+ *
47
+ * @param locale - The locale to load translations for
48
+ * @returns TranslationSet with all 18 keys
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * const translations = TranslationLoader.loadDefaults('de');
53
+ * console.log(translations.galleryTitle); // 'Bilder auswählen'
54
+ * ```
55
+ */
56
+ static loadDefaults(locale: SupportedLocale): TranslationSet;
57
+ /**
58
+ * Validate that a locale is supported.
59
+ *
60
+ * @param locale - The locale to validate
61
+ * @throws {Error} If locale is not a SupportedLocale
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * TranslationLoader.validateLocale('en'); // OK
66
+ * TranslationLoader.validateLocale('ja'); // Error: Invalid locale 'ja'
67
+ * ```
68
+ */
69
+ static validateLocale(locale: string): asserts locale is SupportedLocale;
70
+ /**
71
+ * Validate that custom text keys match TranslationSet keys.
72
+ *
73
+ * @param customTexts - Partial custom overrides
74
+ * @throws {Error} If any key in customTexts is not a valid TranslationSet key
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * TranslationLoader.validateCustomTexts({ galleryTitle: 'Custom' }); // OK
79
+ * TranslationLoader.validateCustomTexts({ invalidKey: 'X' }); // Error
80
+ * ```
81
+ */
82
+ static validateCustomTexts(customTexts: Partial<TranslationSet>): void;
83
+ /**
84
+ * Merge custom overrides on top of default translations.
85
+ *
86
+ * Creates a defensive copy to prevent mutation.
87
+ *
88
+ * @param defaults - Default TranslationSet
89
+ * @param customTexts - Partial custom overrides
90
+ * @returns Merged TranslationSet with all 18 keys
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * const defaults = TranslationLoader.loadDefaults('en');
95
+ * const merged = TranslationLoader.merge(defaults, {
96
+ * galleryTitle: 'My Custom Title',
97
+ * });
98
+ * console.log(merged.galleryTitle); // 'My Custom Title'
99
+ * console.log(merged.selectButton); // 'Select' (from defaults)
100
+ * ```
101
+ */
102
+ static merge(defaults: TranslationSet, customTexts?: Partial<TranslationSet>): TranslationSet;
103
+ /**
104
+ * Load and merge translations based on configuration.
105
+ *
106
+ * This is the main entry point for translation loading.
107
+ *
108
+ * Logic:
109
+ * 1. If locale provided: validate and use it
110
+ * 2. Else: detect system language (fallback to 'en')
111
+ * 3. Load default translations for locale
112
+ * 4. If customTexts provided: validate and merge
113
+ * 5. Return merged TranslationSet
114
+ *
115
+ * @param locale - Optional explicit locale ('en' | 'de' | 'fr' | 'es')
116
+ * @param customTexts - Optional custom text overrides
117
+ * @returns Merged TranslationSet with all 18 keys
118
+ * @throws {Error} If locale is invalid or customTexts has invalid keys
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * // System language detection
123
+ * const t1 = TranslationLoader.loadTranslations();
124
+ *
125
+ * // Explicit locale
126
+ * const t2 = TranslationLoader.loadTranslations('de');
127
+ *
128
+ * // Explicit locale + custom overrides
129
+ * const t3 = TranslationLoader.loadTranslations('en', {
130
+ * galleryTitle: 'Pick Photos',
131
+ * });
132
+ *
133
+ * // System language + custom overrides
134
+ * const t4 = TranslationLoader.loadTranslations(undefined, {
135
+ * confirmButton: 'Done',
136
+ * });
137
+ * ```
138
+ */
139
+ static loadTranslations(locale?: SupportedLocale, customTexts?: Partial<TranslationSet>): Promise<TranslationSet>;
140
+ }
@@ -0,0 +1,218 @@
1
+ import { Device } from '@capacitor/device';
2
+ // Import translation files
3
+ import deTranslations from './translations/de.json';
4
+ import enTranslations from './translations/en.json';
5
+ import esTranslations from './translations/es.json';
6
+ import frTranslations from './translations/fr.json';
7
+ /**
8
+ * Translation loader utility for language detection and merging.
9
+ *
10
+ * Handles:
11
+ * - System language detection via navigator.language
12
+ * - Default translation loading
13
+ * - Custom translation merging
14
+ * - Validation of locale and custom keys
15
+ *
16
+ * @internal
17
+ */
18
+ export class TranslationLoader {
19
+ /**
20
+ * Detect system language from navigator.language.
21
+ *
22
+ * Logic:
23
+ * 1. Get navigator.language (e.g., 'de-DE', 'fr-FR', 'en-US')
24
+ * 2. Extract language code (first 2 chars: 'de', 'fr', 'en')
25
+ * 3. Check if supported ('en' | 'de' | 'fr' | 'es')
26
+ * 4. Fallback to 'en' if not supported
27
+ *
28
+ * @returns Detected SupportedLocale, fallback to 'en'
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * // navigator.language = 'de-DE'
33
+ * detectSystemLanguage(); // 'de'
34
+ *
35
+ * // navigator.language = 'ja-JP' (not supported)
36
+ * detectSystemLanguage(); // 'en' (fallback)
37
+ * ```
38
+ */
39
+ static async detectSystemLanguage() {
40
+ try {
41
+ const deviceInfo = await Device.getLanguageCode();
42
+ const langCode = deviceInfo.value.split('-')[0].toLowerCase();
43
+ const supportedLocales = ['en', 'de', 'fr', 'es'];
44
+ if (supportedLocales.includes(langCode)) {
45
+ return langCode;
46
+ }
47
+ }
48
+ catch (error) {
49
+ console.warn('[TranslationLoader] Failed to detect device language, falling back to English:', error);
50
+ }
51
+ return 'en'; // Fallback to English
52
+ }
53
+ /**
54
+ * Load default translations for a given locale.
55
+ *
56
+ * @param locale - The locale to load translations for
57
+ * @returns TranslationSet with all 18 keys
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * const translations = TranslationLoader.loadDefaults('de');
62
+ * console.log(translations.galleryTitle); // 'Bilder auswählen'
63
+ * ```
64
+ */
65
+ static loadDefaults(locale) {
66
+ return Object.assign({}, this.DEFAULT_TRANSLATIONS[locale]);
67
+ }
68
+ /**
69
+ * Validate that a locale is supported.
70
+ *
71
+ * @param locale - The locale to validate
72
+ * @throws {Error} If locale is not a SupportedLocale
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * TranslationLoader.validateLocale('en'); // OK
77
+ * TranslationLoader.validateLocale('ja'); // Error: Invalid locale 'ja'
78
+ * ```
79
+ */
80
+ static validateLocale(locale) {
81
+ const supportedLocales = ['en', 'de', 'fr', 'es'];
82
+ if (!supportedLocales.includes(locale)) {
83
+ throw new Error(`Invalid locale '${locale}'. Supported locales: ${supportedLocales.join(', ')}`);
84
+ }
85
+ }
86
+ /**
87
+ * Validate that custom text keys match TranslationSet keys.
88
+ *
89
+ * @param customTexts - Partial custom overrides
90
+ * @throws {Error} If any key in customTexts is not a valid TranslationSet key
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * TranslationLoader.validateCustomTexts({ galleryTitle: 'Custom' }); // OK
95
+ * TranslationLoader.validateCustomTexts({ invalidKey: 'X' }); // Error
96
+ * ```
97
+ */
98
+ static validateCustomTexts(customTexts) {
99
+ const invalidKeys = Object.keys(customTexts).filter((key) => !this.REQUIRED_KEYS.includes(key));
100
+ if (invalidKeys.length > 0) {
101
+ throw new Error(`Invalid customTexts keys: ${invalidKeys.join(', ')}. ` + `Valid keys: ${this.REQUIRED_KEYS.join(', ')}`);
102
+ }
103
+ }
104
+ /**
105
+ * Merge custom overrides on top of default translations.
106
+ *
107
+ * Creates a defensive copy to prevent mutation.
108
+ *
109
+ * @param defaults - Default TranslationSet
110
+ * @param customTexts - Partial custom overrides
111
+ * @returns Merged TranslationSet with all 18 keys
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * const defaults = TranslationLoader.loadDefaults('en');
116
+ * const merged = TranslationLoader.merge(defaults, {
117
+ * galleryTitle: 'My Custom Title',
118
+ * });
119
+ * console.log(merged.galleryTitle); // 'My Custom Title'
120
+ * console.log(merged.selectButton); // 'Select' (from defaults)
121
+ * ```
122
+ */
123
+ static merge(defaults, customTexts) {
124
+ if (!customTexts) {
125
+ return Object.assign({}, defaults);
126
+ }
127
+ return Object.assign(Object.assign({}, defaults), customTexts);
128
+ }
129
+ /**
130
+ * Load and merge translations based on configuration.
131
+ *
132
+ * This is the main entry point for translation loading.
133
+ *
134
+ * Logic:
135
+ * 1. If locale provided: validate and use it
136
+ * 2. Else: detect system language (fallback to 'en')
137
+ * 3. Load default translations for locale
138
+ * 4. If customTexts provided: validate and merge
139
+ * 5. Return merged TranslationSet
140
+ *
141
+ * @param locale - Optional explicit locale ('en' | 'de' | 'fr' | 'es')
142
+ * @param customTexts - Optional custom text overrides
143
+ * @returns Merged TranslationSet with all 18 keys
144
+ * @throws {Error} If locale is invalid or customTexts has invalid keys
145
+ *
146
+ * @example
147
+ * ```typescript
148
+ * // System language detection
149
+ * const t1 = TranslationLoader.loadTranslations();
150
+ *
151
+ * // Explicit locale
152
+ * const t2 = TranslationLoader.loadTranslations('de');
153
+ *
154
+ * // Explicit locale + custom overrides
155
+ * const t3 = TranslationLoader.loadTranslations('en', {
156
+ * galleryTitle: 'Pick Photos',
157
+ * });
158
+ *
159
+ * // System language + custom overrides
160
+ * const t4 = TranslationLoader.loadTranslations(undefined, {
161
+ * confirmButton: 'Done',
162
+ * });
163
+ * ```
164
+ */
165
+ static async loadTranslations(locale, customTexts) {
166
+ // Determine locale to use
167
+ let targetLocale;
168
+ if (locale !== undefined) {
169
+ this.validateLocale(locale);
170
+ targetLocale = locale;
171
+ }
172
+ else {
173
+ targetLocale = await this.detectSystemLanguage();
174
+ }
175
+ // Load default translations
176
+ const defaults = this.loadDefaults(targetLocale);
177
+ // Validate and merge custom overrides
178
+ if (customTexts !== undefined) {
179
+ this.validateCustomTexts(customTexts);
180
+ return this.merge(defaults, customTexts);
181
+ }
182
+ return defaults;
183
+ }
184
+ }
185
+ /**
186
+ * All required keys in a valid TranslationSet.
187
+ * Used for validation of custom overrides.
188
+ */
189
+ TranslationLoader.REQUIRED_KEYS = [
190
+ 'galleryTitle',
191
+ 'selectButton',
192
+ 'cancelButton',
193
+ 'selectAllButton',
194
+ 'deselectAllButton',
195
+ 'selectionCounter',
196
+ 'confirmButton',
197
+ 'filterDialogTitle',
198
+ 'radiusLabel',
199
+ 'startDateLabel',
200
+ 'endDateLabel',
201
+ 'loadingMessage',
202
+ 'emptyMessage',
203
+ 'errorMessage',
204
+ 'retryButton',
205
+ 'initializationError',
206
+ 'permissionError',
207
+ 'filterError',
208
+ ];
209
+ /**
210
+ * Default translations by locale.
211
+ */
212
+ TranslationLoader.DEFAULT_TRANSLATIONS = {
213
+ en: enTranslations,
214
+ de: deTranslations,
215
+ fr: frTranslations,
216
+ es: esTranslations,
217
+ };
218
+ //# sourceMappingURL=TranslationLoader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TranslationLoader.js","sourceRoot":"","sources":["../../src/TranslationLoader.ts"],"names":[],"mappings":"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","sourcesContent":["import { Device } from '@capacitor/device';\n\nimport type { SupportedLocale, TranslationSet } from './definitions';\n// Import translation files\nimport deTranslations from './translations/de.json';\nimport enTranslations from './translations/en.json';\nimport esTranslations from './translations/es.json';\nimport frTranslations from './translations/fr.json';\n\n/**\n * Translation loader utility for language detection and merging.\n *\n * Handles:\n * - System language detection via navigator.language\n * - Default translation loading\n * - Custom translation merging\n * - Validation of locale and custom keys\n *\n * @internal\n */\nexport class TranslationLoader {\n /**\n * All required keys in a valid TranslationSet.\n * Used for validation of custom overrides.\n */\n private static readonly REQUIRED_KEYS: (keyof TranslationSet)[] = [\n 'galleryTitle',\n 'selectButton',\n 'cancelButton',\n 'selectAllButton',\n 'deselectAllButton',\n 'selectionCounter',\n 'confirmButton',\n 'filterDialogTitle',\n 'radiusLabel',\n 'startDateLabel',\n 'endDateLabel',\n 'loadingMessage',\n 'emptyMessage',\n 'errorMessage',\n 'retryButton',\n 'initializationError',\n 'permissionError',\n 'filterError',\n ];\n\n /**\n * Default translations by locale.\n */\n private static readonly DEFAULT_TRANSLATIONS: Record<SupportedLocale, TranslationSet> = {\n en: enTranslations as TranslationSet,\n de: deTranslations as TranslationSet,\n fr: frTranslations as TranslationSet,\n es: esTranslations as TranslationSet,\n };\n\n /**\n * Detect system language from navigator.language.\n *\n * Logic:\n * 1. Get navigator.language (e.g., 'de-DE', 'fr-FR', 'en-US')\n * 2. Extract language code (first 2 chars: 'de', 'fr', 'en')\n * 3. Check if supported ('en' | 'de' | 'fr' | 'es')\n * 4. Fallback to 'en' if not supported\n *\n * @returns Detected SupportedLocale, fallback to 'en'\n *\n * @example\n * ```typescript\n * // navigator.language = 'de-DE'\n * detectSystemLanguage(); // 'de'\n *\n * // navigator.language = 'ja-JP' (not supported)\n * detectSystemLanguage(); // 'en' (fallback)\n * ```\n */\n public static async detectSystemLanguage(): Promise<SupportedLocale> {\n try {\n const deviceInfo = await Device.getLanguageCode();\n const langCode = deviceInfo.value.split('-')[0].toLowerCase();\n\n const supportedLocales: SupportedLocale[] = ['en', 'de', 'fr', 'es'];\n if (supportedLocales.includes(langCode as SupportedLocale)) {\n return langCode as SupportedLocale;\n }\n } catch (error) {\n console.warn('[TranslationLoader] Failed to detect device language, falling back to English:', error);\n }\n\n return 'en'; // Fallback to English\n }\n\n /**\n * Load default translations for a given locale.\n *\n * @param locale - The locale to load translations for\n * @returns TranslationSet with all 18 keys\n *\n * @example\n * ```typescript\n * const translations = TranslationLoader.loadDefaults('de');\n * console.log(translations.galleryTitle); // 'Bilder auswählen'\n * ```\n */\n public static loadDefaults(locale: SupportedLocale): TranslationSet {\n return { ...this.DEFAULT_TRANSLATIONS[locale] };\n }\n\n /**\n * Validate that a locale is supported.\n *\n * @param locale - The locale to validate\n * @throws {Error} If locale is not a SupportedLocale\n *\n * @example\n * ```typescript\n * TranslationLoader.validateLocale('en'); // OK\n * TranslationLoader.validateLocale('ja'); // Error: Invalid locale 'ja'\n * ```\n */\n public static validateLocale(locale: string): asserts locale is SupportedLocale {\n const supportedLocales: readonly string[] = ['en', 'de', 'fr', 'es'];\n if (!supportedLocales.includes(locale)) {\n throw new Error(`Invalid locale '${locale}'. Supported locales: ${supportedLocales.join(', ')}`);\n }\n }\n\n /**\n * Validate that custom text keys match TranslationSet keys.\n *\n * @param customTexts - Partial custom overrides\n * @throws {Error} If any key in customTexts is not a valid TranslationSet key\n *\n * @example\n * ```typescript\n * TranslationLoader.validateCustomTexts({ galleryTitle: 'Custom' }); // OK\n * TranslationLoader.validateCustomTexts({ invalidKey: 'X' }); // Error\n * ```\n */\n public static validateCustomTexts(customTexts: Partial<TranslationSet>): void {\n const invalidKeys = Object.keys(customTexts).filter(\n (key) => !this.REQUIRED_KEYS.includes(key as keyof TranslationSet),\n );\n\n if (invalidKeys.length > 0) {\n throw new Error(\n `Invalid customTexts keys: ${invalidKeys.join(', ')}. ` + `Valid keys: ${this.REQUIRED_KEYS.join(', ')}`,\n );\n }\n }\n\n /**\n * Merge custom overrides on top of default translations.\n *\n * Creates a defensive copy to prevent mutation.\n *\n * @param defaults - Default TranslationSet\n * @param customTexts - Partial custom overrides\n * @returns Merged TranslationSet with all 18 keys\n *\n * @example\n * ```typescript\n * const defaults = TranslationLoader.loadDefaults('en');\n * const merged = TranslationLoader.merge(defaults, {\n * galleryTitle: 'My Custom Title',\n * });\n * console.log(merged.galleryTitle); // 'My Custom Title'\n * console.log(merged.selectButton); // 'Select' (from defaults)\n * ```\n */\n public static merge(defaults: TranslationSet, customTexts?: Partial<TranslationSet>): TranslationSet {\n if (!customTexts) {\n return { ...defaults };\n }\n\n return {\n ...defaults,\n ...customTexts,\n };\n }\n\n /**\n * Load and merge translations based on configuration.\n *\n * This is the main entry point for translation loading.\n *\n * Logic:\n * 1. If locale provided: validate and use it\n * 2. Else: detect system language (fallback to 'en')\n * 3. Load default translations for locale\n * 4. If customTexts provided: validate and merge\n * 5. Return merged TranslationSet\n *\n * @param locale - Optional explicit locale ('en' | 'de' | 'fr' | 'es')\n * @param customTexts - Optional custom text overrides\n * @returns Merged TranslationSet with all 18 keys\n * @throws {Error} If locale is invalid or customTexts has invalid keys\n *\n * @example\n * ```typescript\n * // System language detection\n * const t1 = TranslationLoader.loadTranslations();\n *\n * // Explicit locale\n * const t2 = TranslationLoader.loadTranslations('de');\n *\n * // Explicit locale + custom overrides\n * const t3 = TranslationLoader.loadTranslations('en', {\n * galleryTitle: 'Pick Photos',\n * });\n *\n * // System language + custom overrides\n * const t4 = TranslationLoader.loadTranslations(undefined, {\n * confirmButton: 'Done',\n * });\n * ```\n */\n public static async loadTranslations(\n locale?: SupportedLocale,\n customTexts?: Partial<TranslationSet>,\n ): Promise<TranslationSet> {\n // Determine locale to use\n let targetLocale: SupportedLocale;\n if (locale !== undefined) {\n this.validateLocale(locale);\n targetLocale = locale;\n } else {\n targetLocale = await this.detectSystemLanguage();\n }\n\n // Load default translations\n const defaults = this.loadDefaults(targetLocale);\n\n // Validate and merge custom overrides\n if (customTexts !== undefined) {\n this.validateCustomTexts(customTexts);\n return this.merge(defaults, customTexts);\n }\n\n return defaults;\n }\n}\n"]}