@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.
- package/CapacitorExifGallery.podspec +17 -0
- package/LICENSE +8 -0
- package/Package.swift +28 -0
- package/README.md +813 -0
- package/dist/esm/ExifGalleryImpl.d.ts +176 -0
- package/dist/esm/ExifGalleryImpl.js +295 -0
- package/dist/esm/ExifGalleryImpl.js.map +1 -0
- package/dist/esm/FilterValidator.d.ts +126 -0
- package/dist/esm/FilterValidator.js +274 -0
- package/dist/esm/FilterValidator.js.map +1 -0
- package/dist/esm/PluginState.d.ts +128 -0
- package/dist/esm/PluginState.js +166 -0
- package/dist/esm/PluginState.js.map +1 -0
- package/dist/esm/PolylineDecoder.d.ts +23 -0
- package/dist/esm/PolylineDecoder.js +34 -0
- package/dist/esm/PolylineDecoder.js.map +1 -0
- package/dist/esm/TranslationLoader.d.ts +140 -0
- package/dist/esm/TranslationLoader.js +218 -0
- package/dist/esm/TranslationLoader.js.map +1 -0
- package/dist/esm/definitions.d.ts +539 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/errors.d.ts +252 -0
- package/dist/esm/errors.js +276 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +40 -0
- package/dist/esm/index.js +42 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/translations/de.json +20 -0
- package/dist/esm/translations/en.json +20 -0
- package/dist/esm/translations/es.json +20 -0
- package/dist/esm/translations/fr.json +20 -0
- package/dist/esm/web.d.ts +6 -0
- package/dist/esm/web.js +14 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +1820 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +1823 -0
- package/dist/plugin.js.map +1 -0
- 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":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG3C,2BAA2B;AAC3B,OAAO,cAAc,MAAM,wBAAwB,CAAC;AACpD,OAAO,cAAc,MAAM,wBAAwB,CAAC;AACpD,OAAO,cAAc,MAAM,wBAAwB,CAAC;AACpD,OAAO,cAAc,MAAM,wBAAwB,CAAC;AAEpD;;;;;;;;;;GAUG;AACH,MAAM,OAAO,iBAAiB;IAoC5B;;;;;;;;;;;;;;;;;;;OAmBG;IACI,MAAM,CAAC,KAAK,CAAC,oBAAoB;QACtC,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,eAAe,EAAE,CAAC;YAClD,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;YAE9D,MAAM,gBAAgB,GAAsB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;YACrE,IAAI,gBAAgB,CAAC,QAAQ,CAAC,QAA2B,CAAC,EAAE,CAAC;gBAC3D,OAAO,QAA2B,CAAC;YACrC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,gFAAgF,EAAE,KAAK,CAAC,CAAC;QACxG,CAAC;QAED,OAAO,IAAI,CAAC,CAAC,sBAAsB;IACrC,CAAC;IAED;;;;;;;;;;;OAWG;IACI,MAAM,CAAC,YAAY,CAAC,MAAuB;QAChD,yBAAY,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,EAAG;IAClD,CAAC;IAED;;;;;;;;;;;OAWG;IACI,MAAM,CAAC,cAAc,CAAC,MAAc;QACzC,MAAM,gBAAgB,GAAsB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QACrE,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,yBAAyB,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnG,CAAC;IACH,CAAC;IAED;;;;;;;;;;;OAWG;IACI,MAAM,CAAC,mBAAmB,CAAC,WAAoC;QACpE,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,CACjD,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,GAA2B,CAAC,CACnE,CAAC;QAEF,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CACb,6BAA6B,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,eAAe,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACzG,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACI,MAAM,CAAC,KAAK,CAAC,QAAwB,EAAE,WAAqC;QACjF,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,yBAAY,QAAQ,EAAG;QACzB,CAAC;QAED,uCACK,QAAQ,GACR,WAAW,EACd;IACJ,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACI,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAClC,MAAwB,EACxB,WAAqC;QAErC,0BAA0B;QAC1B,IAAI,YAA6B,CAAC;QAClC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;YAC5B,YAAY,GAAG,MAAM,CAAC;QACxB,CAAC;aAAM,CAAC;YACN,YAAY,GAAG,MAAM,IAAI,CAAC,oBAAoB,EAAE,CAAC;QACnD,CAAC;QAED,4BAA4B;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAEjD,sCAAsC;QACtC,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,IAAI,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC;YACtC,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC3C,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;;AA3ND;;;GAGG;AACqB,+BAAa,GAA6B;IAChE,cAAc;IACd,cAAc;IACd,cAAc;IACd,iBAAiB;IACjB,mBAAmB;IACnB,kBAAkB;IAClB,eAAe;IACf,mBAAmB;IACnB,aAAa;IACb,gBAAgB;IAChB,cAAc;IACd,gBAAgB;IAChB,cAAc;IACd,cAAc;IACd,aAAa;IACb,qBAAqB;IACrB,iBAAiB;IACjB,aAAa;CACd,CAAC;AAEF;;GAEG;AACqB,sCAAoB,GAA4C;IACtF,EAAE,EAAE,cAAgC;IACpC,EAAE,EAAE,cAAgC;IACpC,EAAE,EAAE,cAAgC;IACpC,EAAE,EAAE,cAAgC;CACrC,CAAC","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"]}
|