map-gl-offline 0.1.0-alpha.7 → 0.1.1
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 +9 -131
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +3177 -269
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +3178 -268
- package/dist/index.js.map +1 -1
- package/dist/style.css +79 -0
- package/dist/ui/components/shared/LanguageSelector.d.ts +22 -0
- package/dist/ui/managers/PanelManager.d.ts +2 -0
- package/dist/ui/translations/ar.d.ts +182 -0
- package/dist/ui/translations/en.d.ts +181 -0
- package/dist/ui/translations/index.d.ts +61 -0
- package/dist/utils/icons.d.ts +1 -0
- package/package.json +4 -2
package/dist/index.esm.js
CHANGED
|
@@ -88,7 +88,7 @@ var LogLevel;
|
|
|
88
88
|
* Main Logger class providing centralized logging functionality.
|
|
89
89
|
* @internal Use the exported `logger` singleton or `configureLogger()` function.
|
|
90
90
|
*/
|
|
91
|
-
class Logger {
|
|
91
|
+
let Logger$1 = class Logger {
|
|
92
92
|
level = LogLevel.ERROR; // Default to ERROR only for production
|
|
93
93
|
/**
|
|
94
94
|
* Configure the logger settings.
|
|
@@ -197,7 +197,7 @@ class Logger {
|
|
|
197
197
|
scope(prefix) {
|
|
198
198
|
return new ScopedLogger(prefix, this);
|
|
199
199
|
}
|
|
200
|
-
}
|
|
200
|
+
};
|
|
201
201
|
/**
|
|
202
202
|
* A scoped logger that prefixes all messages with a module/component name.
|
|
203
203
|
* Created using `logger.scope('PrefixName')`.
|
|
@@ -253,7 +253,7 @@ class ScopedLogger {
|
|
|
253
253
|
* logger.error('Something went wrong', error);
|
|
254
254
|
* ```
|
|
255
255
|
*/
|
|
256
|
-
const logger = new Logger();
|
|
256
|
+
const logger = new Logger$1();
|
|
257
257
|
/**
|
|
258
258
|
* Configure the global logger settings.
|
|
259
259
|
* Call this early in your application to set logging preferences.
|
|
@@ -737,6 +737,7 @@ const TABLER_ICONS = {
|
|
|
737
737
|
upload: '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-upload" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2" /><polyline points="7,9 12,4 17,9" /><line x1="12" y1="4" x2="12" y2="16" /></svg>',
|
|
738
738
|
infoCircle: '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-info-circle" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><circle cx="12" cy="12" r="9" /><line x1="12" y1="8" x2="12.01" y2="8" /><polyline points="11,12 12,12 12,16 13,16" /></svg>',
|
|
739
739
|
deviceDesktop: '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-device-desktop" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><rect x="3" y="4" width="18" height="12" rx="1" /><line x1="7" y1="20" x2="17" y2="20" /><line x1="9" y1="16" x2="9" y2="20" /><line x1="15" y1="16" x2="15" y2="20" /></svg>',
|
|
740
|
+
language: '<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-language" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 5h7" /><path d="M9 3v2c0 4.418 -2.239 8 -5 8" /><path d="M5 9c0 2.144 2.952 3.908 6.7 4" /><path d="M12 20l4 -9l4 9" /><path d="M19.1 18h-6.2" /></svg>',
|
|
740
741
|
};
|
|
741
742
|
/**
|
|
742
743
|
* Default icon configuration
|
|
@@ -817,6 +818,8 @@ const icons = {
|
|
|
817
818
|
infoCircle: (config) => renderIcon(TABLER_ICONS.infoCircle, config),
|
|
818
819
|
// System/Device icons
|
|
819
820
|
deviceDesktop: (config) => renderIcon(TABLER_ICONS.deviceDesktop, config),
|
|
821
|
+
// Language icons
|
|
822
|
+
language: (config) => renderIcon(TABLER_ICONS.language, config),
|
|
820
823
|
};
|
|
821
824
|
/**
|
|
822
825
|
* Helper function to get an icon by name
|
|
@@ -6868,155 +6871,2909 @@ async function downloadStyleWithProvider(styleUrl, options = {}) {
|
|
|
6868
6871
|
},
|
|
6869
6872
|
};
|
|
6870
6873
|
}
|
|
6871
|
-
catch (error) {
|
|
6872
|
-
logger.error(`Failed to download style from ${styleUrl}:`, error);
|
|
6873
|
-
return {
|
|
6874
|
-
styleId: '',
|
|
6875
|
-
success: false,
|
|
6876
|
-
downloadTime: Date.now() - startTime,
|
|
6877
|
-
styleSize: 0,
|
|
6878
|
-
sourcesProcessed: 0,
|
|
6879
|
-
sourcesEmbedded: 0,
|
|
6880
|
-
errors: [error instanceof Error ? error.message : String(error)],
|
|
6881
|
-
analytics: {
|
|
6882
|
-
sourceTypes: {},
|
|
6883
|
-
layerTypes: {},
|
|
6884
|
-
totalLayers: 0,
|
|
6885
|
-
hasGlyphs: false,
|
|
6886
|
-
hasSprites: false,
|
|
6887
|
-
},
|
|
6874
|
+
catch (error) {
|
|
6875
|
+
logger.error(`Failed to download style from ${styleUrl}:`, error);
|
|
6876
|
+
return {
|
|
6877
|
+
styleId: '',
|
|
6878
|
+
success: false,
|
|
6879
|
+
downloadTime: Date.now() - startTime,
|
|
6880
|
+
styleSize: 0,
|
|
6881
|
+
sourcesProcessed: 0,
|
|
6882
|
+
sourcesEmbedded: 0,
|
|
6883
|
+
errors: [error instanceof Error ? error.message : String(error)],
|
|
6884
|
+
analytics: {
|
|
6885
|
+
sourceTypes: {},
|
|
6886
|
+
layerTypes: {},
|
|
6887
|
+
totalLayers: 0,
|
|
6888
|
+
hasGlyphs: false,
|
|
6889
|
+
hasSprites: false,
|
|
6890
|
+
},
|
|
6891
|
+
};
|
|
6892
|
+
}
|
|
6893
|
+
}
|
|
6894
|
+
|
|
6895
|
+
var styleService = /*#__PURE__*/Object.freeze({
|
|
6896
|
+
__proto__: null,
|
|
6897
|
+
cleanupOldStyles: cleanupOldStyles,
|
|
6898
|
+
deleteStyleById: deleteStyleById,
|
|
6899
|
+
deleteStyles: deleteStyles,
|
|
6900
|
+
downloadStyleWithProvider: downloadStyleWithProvider,
|
|
6901
|
+
downloadStyles: downloadStyles,
|
|
6902
|
+
getStyleStats: getStyleStats,
|
|
6903
|
+
isStyleDownloaded: isStyleDownloaded,
|
|
6904
|
+
loadStyleById: loadStyleById,
|
|
6905
|
+
loadStyles: loadStyles
|
|
6906
|
+
});
|
|
6907
|
+
|
|
6908
|
+
/**
|
|
6909
|
+
* Simplified theme system for dark/light/system mode management
|
|
6910
|
+
* Now that all components use Tailwind CSS, we only need basic theme switching
|
|
6911
|
+
*/
|
|
6912
|
+
const lightTheme = {
|
|
6913
|
+
mode: 'light',
|
|
6914
|
+
preference: 'light',
|
|
6915
|
+
};
|
|
6916
|
+
// Theme context management
|
|
6917
|
+
class ThemeManager {
|
|
6918
|
+
currentTheme = lightTheme;
|
|
6919
|
+
listeners = [];
|
|
6920
|
+
constructor() {
|
|
6921
|
+
// Initialize theme from localStorage or system preference
|
|
6922
|
+
this.initializeTheme();
|
|
6923
|
+
}
|
|
6924
|
+
initializeTheme() {
|
|
6925
|
+
const savedPreference = localStorage.getItem('offline-manager-theme');
|
|
6926
|
+
if (savedPreference === 'system' || !savedPreference) {
|
|
6927
|
+
// Use system preference
|
|
6928
|
+
this.setThemePreference('system');
|
|
6929
|
+
}
|
|
6930
|
+
else {
|
|
6931
|
+
this.setThemePreference(savedPreference);
|
|
6932
|
+
}
|
|
6933
|
+
// Listen for system theme changes
|
|
6934
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
|
6935
|
+
if (this.currentTheme.preference === 'system') {
|
|
6936
|
+
this.applyThemeMode(e.matches ? 'dark' : 'light', 'system');
|
|
6937
|
+
}
|
|
6938
|
+
});
|
|
6939
|
+
}
|
|
6940
|
+
getTheme() {
|
|
6941
|
+
return this.currentTheme;
|
|
6942
|
+
}
|
|
6943
|
+
/**
|
|
6944
|
+
* Get the effective theme mode (light or dark)
|
|
6945
|
+
*/
|
|
6946
|
+
getEffectiveMode() {
|
|
6947
|
+
return this.currentTheme.mode;
|
|
6948
|
+
}
|
|
6949
|
+
/**
|
|
6950
|
+
* Get the user's preference (light, dark, or system)
|
|
6951
|
+
*/
|
|
6952
|
+
getPreference() {
|
|
6953
|
+
return this.currentTheme.preference;
|
|
6954
|
+
}
|
|
6955
|
+
applyThemeMode(mode, preference) {
|
|
6956
|
+
this.currentTheme = { mode, preference };
|
|
6957
|
+
// Apply theme to document for Tailwind dark mode
|
|
6958
|
+
if (mode === 'dark') {
|
|
6959
|
+
document.documentElement.classList.add('dark');
|
|
6960
|
+
}
|
|
6961
|
+
else {
|
|
6962
|
+
document.documentElement.classList.remove('dark');
|
|
6963
|
+
}
|
|
6964
|
+
this.notifyListeners();
|
|
6965
|
+
}
|
|
6966
|
+
/**
|
|
6967
|
+
* Set the theme preference (light, dark, or system)
|
|
6968
|
+
*/
|
|
6969
|
+
setThemePreference(preference) {
|
|
6970
|
+
localStorage.setItem('offline-manager-theme', preference);
|
|
6971
|
+
if (preference === 'system') {
|
|
6972
|
+
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
6973
|
+
this.applyThemeMode(systemPrefersDark ? 'dark' : 'light', 'system');
|
|
6974
|
+
}
|
|
6975
|
+
else {
|
|
6976
|
+
this.applyThemeMode(preference, preference);
|
|
6977
|
+
}
|
|
6978
|
+
}
|
|
6979
|
+
/**
|
|
6980
|
+
* Set the theme mode directly (for backwards compatibility)
|
|
6981
|
+
* @deprecated Use setThemePreference instead
|
|
6982
|
+
*/
|
|
6983
|
+
setTheme(mode) {
|
|
6984
|
+
this.setThemePreference(mode);
|
|
6985
|
+
}
|
|
6986
|
+
/**
|
|
6987
|
+
* Cycle through themes: light -> dark -> system -> light
|
|
6988
|
+
*/
|
|
6989
|
+
cycleTheme() {
|
|
6990
|
+
const preference = this.currentTheme.preference;
|
|
6991
|
+
if (preference === 'light') {
|
|
6992
|
+
this.setThemePreference('dark');
|
|
6993
|
+
}
|
|
6994
|
+
else if (preference === 'dark') {
|
|
6995
|
+
this.setThemePreference('system');
|
|
6996
|
+
}
|
|
6997
|
+
else {
|
|
6998
|
+
this.setThemePreference('light');
|
|
6999
|
+
}
|
|
7000
|
+
}
|
|
7001
|
+
/**
|
|
7002
|
+
* Toggle between light and dark (legacy behavior)
|
|
7003
|
+
* @deprecated Use cycleTheme for light/dark/system cycling
|
|
7004
|
+
*/
|
|
7005
|
+
toggleTheme() {
|
|
7006
|
+
this.cycleTheme();
|
|
7007
|
+
}
|
|
7008
|
+
subscribe(listener) {
|
|
7009
|
+
this.listeners.push(listener);
|
|
7010
|
+
return () => {
|
|
7011
|
+
const index = this.listeners.indexOf(listener);
|
|
7012
|
+
if (index > -1) {
|
|
7013
|
+
this.listeners.splice(index, 1);
|
|
7014
|
+
}
|
|
7015
|
+
};
|
|
7016
|
+
}
|
|
7017
|
+
notifyListeners() {
|
|
7018
|
+
this.listeners.forEach(listener => listener(this.currentTheme));
|
|
7019
|
+
}
|
|
7020
|
+
}
|
|
7021
|
+
// Export singleton instance
|
|
7022
|
+
const themeManager = new ThemeManager();
|
|
7023
|
+
|
|
7024
|
+
const isString = obj => typeof obj === 'string';
|
|
7025
|
+
const defer = () => {
|
|
7026
|
+
let res;
|
|
7027
|
+
let rej;
|
|
7028
|
+
const promise = new Promise((resolve, reject) => {
|
|
7029
|
+
res = resolve;
|
|
7030
|
+
rej = reject;
|
|
7031
|
+
});
|
|
7032
|
+
promise.resolve = res;
|
|
7033
|
+
promise.reject = rej;
|
|
7034
|
+
return promise;
|
|
7035
|
+
};
|
|
7036
|
+
const makeString = object => {
|
|
7037
|
+
if (object == null) return '';
|
|
7038
|
+
return '' + object;
|
|
7039
|
+
};
|
|
7040
|
+
const copy = (a, s, t) => {
|
|
7041
|
+
a.forEach(m => {
|
|
7042
|
+
if (s[m]) t[m] = s[m];
|
|
7043
|
+
});
|
|
7044
|
+
};
|
|
7045
|
+
const lastOfPathSeparatorRegExp = /###/g;
|
|
7046
|
+
const cleanKey = key => key && key.indexOf('###') > -1 ? key.replace(lastOfPathSeparatorRegExp, '.') : key;
|
|
7047
|
+
const canNotTraverseDeeper = object => !object || isString(object);
|
|
7048
|
+
const getLastOfPath = (object, path, Empty) => {
|
|
7049
|
+
const stack = !isString(path) ? path : path.split('.');
|
|
7050
|
+
let stackIndex = 0;
|
|
7051
|
+
while (stackIndex < stack.length - 1) {
|
|
7052
|
+
if (canNotTraverseDeeper(object)) return {};
|
|
7053
|
+
const key = cleanKey(stack[stackIndex]);
|
|
7054
|
+
if (!object[key] && Empty) object[key] = new Empty();
|
|
7055
|
+
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
|
7056
|
+
object = object[key];
|
|
7057
|
+
} else {
|
|
7058
|
+
object = {};
|
|
7059
|
+
}
|
|
7060
|
+
++stackIndex;
|
|
7061
|
+
}
|
|
7062
|
+
if (canNotTraverseDeeper(object)) return {};
|
|
7063
|
+
return {
|
|
7064
|
+
obj: object,
|
|
7065
|
+
k: cleanKey(stack[stackIndex])
|
|
7066
|
+
};
|
|
7067
|
+
};
|
|
7068
|
+
const setPath = (object, path, newValue) => {
|
|
7069
|
+
const {
|
|
7070
|
+
obj,
|
|
7071
|
+
k
|
|
7072
|
+
} = getLastOfPath(object, path, Object);
|
|
7073
|
+
if (obj !== undefined || path.length === 1) {
|
|
7074
|
+
obj[k] = newValue;
|
|
7075
|
+
return;
|
|
7076
|
+
}
|
|
7077
|
+
let e = path[path.length - 1];
|
|
7078
|
+
let p = path.slice(0, path.length - 1);
|
|
7079
|
+
let last = getLastOfPath(object, p, Object);
|
|
7080
|
+
while (last.obj === undefined && p.length) {
|
|
7081
|
+
e = `${p[p.length - 1]}.${e}`;
|
|
7082
|
+
p = p.slice(0, p.length - 1);
|
|
7083
|
+
last = getLastOfPath(object, p, Object);
|
|
7084
|
+
if (last?.obj && typeof last.obj[`${last.k}.${e}`] !== 'undefined') {
|
|
7085
|
+
last.obj = undefined;
|
|
7086
|
+
}
|
|
7087
|
+
}
|
|
7088
|
+
last.obj[`${last.k}.${e}`] = newValue;
|
|
7089
|
+
};
|
|
7090
|
+
const pushPath = (object, path, newValue, concat) => {
|
|
7091
|
+
const {
|
|
7092
|
+
obj,
|
|
7093
|
+
k
|
|
7094
|
+
} = getLastOfPath(object, path, Object);
|
|
7095
|
+
obj[k] = obj[k] || [];
|
|
7096
|
+
obj[k].push(newValue);
|
|
7097
|
+
};
|
|
7098
|
+
const getPath = (object, path) => {
|
|
7099
|
+
const {
|
|
7100
|
+
obj,
|
|
7101
|
+
k
|
|
7102
|
+
} = getLastOfPath(object, path);
|
|
7103
|
+
if (!obj) return undefined;
|
|
7104
|
+
if (!Object.prototype.hasOwnProperty.call(obj, k)) return undefined;
|
|
7105
|
+
return obj[k];
|
|
7106
|
+
};
|
|
7107
|
+
const getPathWithDefaults = (data, defaultData, key) => {
|
|
7108
|
+
const value = getPath(data, key);
|
|
7109
|
+
if (value !== undefined) {
|
|
7110
|
+
return value;
|
|
7111
|
+
}
|
|
7112
|
+
return getPath(defaultData, key);
|
|
7113
|
+
};
|
|
7114
|
+
const deepExtend = (target, source, overwrite) => {
|
|
7115
|
+
for (const prop in source) {
|
|
7116
|
+
if (prop !== '__proto__' && prop !== 'constructor') {
|
|
7117
|
+
if (prop in target) {
|
|
7118
|
+
if (isString(target[prop]) || target[prop] instanceof String || isString(source[prop]) || source[prop] instanceof String) {
|
|
7119
|
+
if (overwrite) target[prop] = source[prop];
|
|
7120
|
+
} else {
|
|
7121
|
+
deepExtend(target[prop], source[prop], overwrite);
|
|
7122
|
+
}
|
|
7123
|
+
} else {
|
|
7124
|
+
target[prop] = source[prop];
|
|
7125
|
+
}
|
|
7126
|
+
}
|
|
7127
|
+
}
|
|
7128
|
+
return target;
|
|
7129
|
+
};
|
|
7130
|
+
const regexEscape = str => str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
|
|
7131
|
+
var _entityMap = {
|
|
7132
|
+
'&': '&',
|
|
7133
|
+
'<': '<',
|
|
7134
|
+
'>': '>',
|
|
7135
|
+
'"': '"',
|
|
7136
|
+
"'": ''',
|
|
7137
|
+
'/': '/'
|
|
7138
|
+
};
|
|
7139
|
+
const escape = data => {
|
|
7140
|
+
if (isString(data)) {
|
|
7141
|
+
return data.replace(/[&<>"'\/]/g, s => _entityMap[s]);
|
|
7142
|
+
}
|
|
7143
|
+
return data;
|
|
7144
|
+
};
|
|
7145
|
+
class RegExpCache {
|
|
7146
|
+
constructor(capacity) {
|
|
7147
|
+
this.capacity = capacity;
|
|
7148
|
+
this.regExpMap = new Map();
|
|
7149
|
+
this.regExpQueue = [];
|
|
7150
|
+
}
|
|
7151
|
+
getRegExp(pattern) {
|
|
7152
|
+
const regExpFromCache = this.regExpMap.get(pattern);
|
|
7153
|
+
if (regExpFromCache !== undefined) {
|
|
7154
|
+
return regExpFromCache;
|
|
7155
|
+
}
|
|
7156
|
+
const regExpNew = new RegExp(pattern);
|
|
7157
|
+
if (this.regExpQueue.length === this.capacity) {
|
|
7158
|
+
this.regExpMap.delete(this.regExpQueue.shift());
|
|
7159
|
+
}
|
|
7160
|
+
this.regExpMap.set(pattern, regExpNew);
|
|
7161
|
+
this.regExpQueue.push(pattern);
|
|
7162
|
+
return regExpNew;
|
|
7163
|
+
}
|
|
7164
|
+
}
|
|
7165
|
+
const chars = [' ', ',', '?', '!', ';'];
|
|
7166
|
+
const looksLikeObjectPathRegExpCache = new RegExpCache(20);
|
|
7167
|
+
const looksLikeObjectPath = (key, nsSeparator, keySeparator) => {
|
|
7168
|
+
nsSeparator = nsSeparator || '';
|
|
7169
|
+
keySeparator = keySeparator || '';
|
|
7170
|
+
const possibleChars = chars.filter(c => nsSeparator.indexOf(c) < 0 && keySeparator.indexOf(c) < 0);
|
|
7171
|
+
if (possibleChars.length === 0) return true;
|
|
7172
|
+
const r = looksLikeObjectPathRegExpCache.getRegExp(`(${possibleChars.map(c => c === '?' ? '\\?' : c).join('|')})`);
|
|
7173
|
+
let matched = !r.test(key);
|
|
7174
|
+
if (!matched) {
|
|
7175
|
+
const ki = key.indexOf(keySeparator);
|
|
7176
|
+
if (ki > 0 && !r.test(key.substring(0, ki))) {
|
|
7177
|
+
matched = true;
|
|
7178
|
+
}
|
|
7179
|
+
}
|
|
7180
|
+
return matched;
|
|
7181
|
+
};
|
|
7182
|
+
const deepFind = (obj, path, keySeparator = '.') => {
|
|
7183
|
+
if (!obj) return undefined;
|
|
7184
|
+
if (obj[path]) {
|
|
7185
|
+
if (!Object.prototype.hasOwnProperty.call(obj, path)) return undefined;
|
|
7186
|
+
return obj[path];
|
|
7187
|
+
}
|
|
7188
|
+
const tokens = path.split(keySeparator);
|
|
7189
|
+
let current = obj;
|
|
7190
|
+
for (let i = 0; i < tokens.length;) {
|
|
7191
|
+
if (!current || typeof current !== 'object') {
|
|
7192
|
+
return undefined;
|
|
7193
|
+
}
|
|
7194
|
+
let next;
|
|
7195
|
+
let nextPath = '';
|
|
7196
|
+
for (let j = i; j < tokens.length; ++j) {
|
|
7197
|
+
if (j !== i) {
|
|
7198
|
+
nextPath += keySeparator;
|
|
7199
|
+
}
|
|
7200
|
+
nextPath += tokens[j];
|
|
7201
|
+
next = current[nextPath];
|
|
7202
|
+
if (next !== undefined) {
|
|
7203
|
+
if (['string', 'number', 'boolean'].indexOf(typeof next) > -1 && j < tokens.length - 1) {
|
|
7204
|
+
continue;
|
|
7205
|
+
}
|
|
7206
|
+
i += j - i + 1;
|
|
7207
|
+
break;
|
|
7208
|
+
}
|
|
7209
|
+
}
|
|
7210
|
+
current = next;
|
|
7211
|
+
}
|
|
7212
|
+
return current;
|
|
7213
|
+
};
|
|
7214
|
+
const getCleanedCode = code => code?.replace('_', '-');
|
|
7215
|
+
|
|
7216
|
+
const consoleLogger = {
|
|
7217
|
+
type: 'logger',
|
|
7218
|
+
log(args) {
|
|
7219
|
+
this.output('log', args);
|
|
7220
|
+
},
|
|
7221
|
+
warn(args) {
|
|
7222
|
+
this.output('warn', args);
|
|
7223
|
+
},
|
|
7224
|
+
error(args) {
|
|
7225
|
+
this.output('error', args);
|
|
7226
|
+
},
|
|
7227
|
+
output(type, args) {
|
|
7228
|
+
console?.[type]?.apply?.(console, args);
|
|
7229
|
+
}
|
|
7230
|
+
};
|
|
7231
|
+
class Logger {
|
|
7232
|
+
constructor(concreteLogger, options = {}) {
|
|
7233
|
+
this.init(concreteLogger, options);
|
|
7234
|
+
}
|
|
7235
|
+
init(concreteLogger, options = {}) {
|
|
7236
|
+
this.prefix = options.prefix || 'i18next:';
|
|
7237
|
+
this.logger = concreteLogger || consoleLogger;
|
|
7238
|
+
this.options = options;
|
|
7239
|
+
this.debug = options.debug;
|
|
7240
|
+
}
|
|
7241
|
+
log(...args) {
|
|
7242
|
+
return this.forward(args, 'log', '', true);
|
|
7243
|
+
}
|
|
7244
|
+
warn(...args) {
|
|
7245
|
+
return this.forward(args, 'warn', '', true);
|
|
7246
|
+
}
|
|
7247
|
+
error(...args) {
|
|
7248
|
+
return this.forward(args, 'error', '');
|
|
7249
|
+
}
|
|
7250
|
+
deprecate(...args) {
|
|
7251
|
+
return this.forward(args, 'warn', 'WARNING DEPRECATED: ', true);
|
|
7252
|
+
}
|
|
7253
|
+
forward(args, lvl, prefix, debugOnly) {
|
|
7254
|
+
if (debugOnly && !this.debug) return null;
|
|
7255
|
+
if (isString(args[0])) args[0] = `${prefix}${this.prefix} ${args[0]}`;
|
|
7256
|
+
return this.logger[lvl](args);
|
|
7257
|
+
}
|
|
7258
|
+
create(moduleName) {
|
|
7259
|
+
return new Logger(this.logger, {
|
|
7260
|
+
...{
|
|
7261
|
+
prefix: `${this.prefix}:${moduleName}:`
|
|
7262
|
+
},
|
|
7263
|
+
...this.options
|
|
7264
|
+
});
|
|
7265
|
+
}
|
|
7266
|
+
clone(options) {
|
|
7267
|
+
options = options || this.options;
|
|
7268
|
+
options.prefix = options.prefix || this.prefix;
|
|
7269
|
+
return new Logger(this.logger, options);
|
|
7270
|
+
}
|
|
7271
|
+
}
|
|
7272
|
+
var baseLogger = new Logger();
|
|
7273
|
+
|
|
7274
|
+
class EventEmitter {
|
|
7275
|
+
constructor() {
|
|
7276
|
+
this.observers = {};
|
|
7277
|
+
}
|
|
7278
|
+
on(events, listener) {
|
|
7279
|
+
events.split(' ').forEach(event => {
|
|
7280
|
+
if (!this.observers[event]) this.observers[event] = new Map();
|
|
7281
|
+
const numListeners = this.observers[event].get(listener) || 0;
|
|
7282
|
+
this.observers[event].set(listener, numListeners + 1);
|
|
7283
|
+
});
|
|
7284
|
+
return this;
|
|
7285
|
+
}
|
|
7286
|
+
off(event, listener) {
|
|
7287
|
+
if (!this.observers[event]) return;
|
|
7288
|
+
if (!listener) {
|
|
7289
|
+
delete this.observers[event];
|
|
7290
|
+
return;
|
|
7291
|
+
}
|
|
7292
|
+
this.observers[event].delete(listener);
|
|
7293
|
+
}
|
|
7294
|
+
emit(event, ...args) {
|
|
7295
|
+
if (this.observers[event]) {
|
|
7296
|
+
const cloned = Array.from(this.observers[event].entries());
|
|
7297
|
+
cloned.forEach(([observer, numTimesAdded]) => {
|
|
7298
|
+
for (let i = 0; i < numTimesAdded; i++) {
|
|
7299
|
+
observer(...args);
|
|
7300
|
+
}
|
|
7301
|
+
});
|
|
7302
|
+
}
|
|
7303
|
+
if (this.observers['*']) {
|
|
7304
|
+
const cloned = Array.from(this.observers['*'].entries());
|
|
7305
|
+
cloned.forEach(([observer, numTimesAdded]) => {
|
|
7306
|
+
for (let i = 0; i < numTimesAdded; i++) {
|
|
7307
|
+
observer.apply(observer, [event, ...args]);
|
|
7308
|
+
}
|
|
7309
|
+
});
|
|
7310
|
+
}
|
|
7311
|
+
}
|
|
7312
|
+
}
|
|
7313
|
+
|
|
7314
|
+
class ResourceStore extends EventEmitter {
|
|
7315
|
+
constructor(data, options = {
|
|
7316
|
+
ns: ['translation'],
|
|
7317
|
+
defaultNS: 'translation'
|
|
7318
|
+
}) {
|
|
7319
|
+
super();
|
|
7320
|
+
this.data = data || {};
|
|
7321
|
+
this.options = options;
|
|
7322
|
+
if (this.options.keySeparator === undefined) {
|
|
7323
|
+
this.options.keySeparator = '.';
|
|
7324
|
+
}
|
|
7325
|
+
if (this.options.ignoreJSONStructure === undefined) {
|
|
7326
|
+
this.options.ignoreJSONStructure = true;
|
|
7327
|
+
}
|
|
7328
|
+
}
|
|
7329
|
+
addNamespaces(ns) {
|
|
7330
|
+
if (this.options.ns.indexOf(ns) < 0) {
|
|
7331
|
+
this.options.ns.push(ns);
|
|
7332
|
+
}
|
|
7333
|
+
}
|
|
7334
|
+
removeNamespaces(ns) {
|
|
7335
|
+
const index = this.options.ns.indexOf(ns);
|
|
7336
|
+
if (index > -1) {
|
|
7337
|
+
this.options.ns.splice(index, 1);
|
|
7338
|
+
}
|
|
7339
|
+
}
|
|
7340
|
+
getResource(lng, ns, key, options = {}) {
|
|
7341
|
+
const keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;
|
|
7342
|
+
const ignoreJSONStructure = options.ignoreJSONStructure !== undefined ? options.ignoreJSONStructure : this.options.ignoreJSONStructure;
|
|
7343
|
+
let path;
|
|
7344
|
+
if (lng.indexOf('.') > -1) {
|
|
7345
|
+
path = lng.split('.');
|
|
7346
|
+
} else {
|
|
7347
|
+
path = [lng, ns];
|
|
7348
|
+
if (key) {
|
|
7349
|
+
if (Array.isArray(key)) {
|
|
7350
|
+
path.push(...key);
|
|
7351
|
+
} else if (isString(key) && keySeparator) {
|
|
7352
|
+
path.push(...key.split(keySeparator));
|
|
7353
|
+
} else {
|
|
7354
|
+
path.push(key);
|
|
7355
|
+
}
|
|
7356
|
+
}
|
|
7357
|
+
}
|
|
7358
|
+
const result = getPath(this.data, path);
|
|
7359
|
+
if (!result && !ns && !key && lng.indexOf('.') > -1) {
|
|
7360
|
+
lng = path[0];
|
|
7361
|
+
ns = path[1];
|
|
7362
|
+
key = path.slice(2).join('.');
|
|
7363
|
+
}
|
|
7364
|
+
if (result || !ignoreJSONStructure || !isString(key)) return result;
|
|
7365
|
+
return deepFind(this.data?.[lng]?.[ns], key, keySeparator);
|
|
7366
|
+
}
|
|
7367
|
+
addResource(lng, ns, key, value, options = {
|
|
7368
|
+
silent: false
|
|
7369
|
+
}) {
|
|
7370
|
+
const keySeparator = options.keySeparator !== undefined ? options.keySeparator : this.options.keySeparator;
|
|
7371
|
+
let path = [lng, ns];
|
|
7372
|
+
if (key) path = path.concat(keySeparator ? key.split(keySeparator) : key);
|
|
7373
|
+
if (lng.indexOf('.') > -1) {
|
|
7374
|
+
path = lng.split('.');
|
|
7375
|
+
value = ns;
|
|
7376
|
+
ns = path[1];
|
|
7377
|
+
}
|
|
7378
|
+
this.addNamespaces(ns);
|
|
7379
|
+
setPath(this.data, path, value);
|
|
7380
|
+
if (!options.silent) this.emit('added', lng, ns, key, value);
|
|
7381
|
+
}
|
|
7382
|
+
addResources(lng, ns, resources, options = {
|
|
7383
|
+
silent: false
|
|
7384
|
+
}) {
|
|
7385
|
+
for (const m in resources) {
|
|
7386
|
+
if (isString(resources[m]) || Array.isArray(resources[m])) this.addResource(lng, ns, m, resources[m], {
|
|
7387
|
+
silent: true
|
|
7388
|
+
});
|
|
7389
|
+
}
|
|
7390
|
+
if (!options.silent) this.emit('added', lng, ns, resources);
|
|
7391
|
+
}
|
|
7392
|
+
addResourceBundle(lng, ns, resources, deep, overwrite, options = {
|
|
7393
|
+
silent: false,
|
|
7394
|
+
skipCopy: false
|
|
7395
|
+
}) {
|
|
7396
|
+
let path = [lng, ns];
|
|
7397
|
+
if (lng.indexOf('.') > -1) {
|
|
7398
|
+
path = lng.split('.');
|
|
7399
|
+
deep = resources;
|
|
7400
|
+
resources = ns;
|
|
7401
|
+
ns = path[1];
|
|
7402
|
+
}
|
|
7403
|
+
this.addNamespaces(ns);
|
|
7404
|
+
let pack = getPath(this.data, path) || {};
|
|
7405
|
+
if (!options.skipCopy) resources = JSON.parse(JSON.stringify(resources));
|
|
7406
|
+
if (deep) {
|
|
7407
|
+
deepExtend(pack, resources, overwrite);
|
|
7408
|
+
} else {
|
|
7409
|
+
pack = {
|
|
7410
|
+
...pack,
|
|
7411
|
+
...resources
|
|
7412
|
+
};
|
|
7413
|
+
}
|
|
7414
|
+
setPath(this.data, path, pack);
|
|
7415
|
+
if (!options.silent) this.emit('added', lng, ns, resources);
|
|
7416
|
+
}
|
|
7417
|
+
removeResourceBundle(lng, ns) {
|
|
7418
|
+
if (this.hasResourceBundle(lng, ns)) {
|
|
7419
|
+
delete this.data[lng][ns];
|
|
7420
|
+
}
|
|
7421
|
+
this.removeNamespaces(ns);
|
|
7422
|
+
this.emit('removed', lng, ns);
|
|
7423
|
+
}
|
|
7424
|
+
hasResourceBundle(lng, ns) {
|
|
7425
|
+
return this.getResource(lng, ns) !== undefined;
|
|
7426
|
+
}
|
|
7427
|
+
getResourceBundle(lng, ns) {
|
|
7428
|
+
if (!ns) ns = this.options.defaultNS;
|
|
7429
|
+
return this.getResource(lng, ns);
|
|
7430
|
+
}
|
|
7431
|
+
getDataByLanguage(lng) {
|
|
7432
|
+
return this.data[lng];
|
|
7433
|
+
}
|
|
7434
|
+
hasLanguageSomeTranslations(lng) {
|
|
7435
|
+
const data = this.getDataByLanguage(lng);
|
|
7436
|
+
const n = data && Object.keys(data) || [];
|
|
7437
|
+
return !!n.find(v => data[v] && Object.keys(data[v]).length > 0);
|
|
7438
|
+
}
|
|
7439
|
+
toJSON() {
|
|
7440
|
+
return this.data;
|
|
7441
|
+
}
|
|
7442
|
+
}
|
|
7443
|
+
|
|
7444
|
+
var postProcessor = {
|
|
7445
|
+
processors: {},
|
|
7446
|
+
addPostProcessor(module) {
|
|
7447
|
+
this.processors[module.name] = module;
|
|
7448
|
+
},
|
|
7449
|
+
handle(processors, value, key, options, translator) {
|
|
7450
|
+
processors.forEach(processor => {
|
|
7451
|
+
value = this.processors[processor]?.process(value, key, options, translator) ?? value;
|
|
7452
|
+
});
|
|
7453
|
+
return value;
|
|
7454
|
+
}
|
|
7455
|
+
};
|
|
7456
|
+
|
|
7457
|
+
const PATH_KEY = Symbol('i18next/PATH_KEY');
|
|
7458
|
+
function createProxy() {
|
|
7459
|
+
const state = [];
|
|
7460
|
+
const handler = Object.create(null);
|
|
7461
|
+
let proxy;
|
|
7462
|
+
handler.get = (target, key) => {
|
|
7463
|
+
proxy?.revoke?.();
|
|
7464
|
+
if (key === PATH_KEY) return state;
|
|
7465
|
+
state.push(key);
|
|
7466
|
+
proxy = Proxy.revocable(target, handler);
|
|
7467
|
+
return proxy.proxy;
|
|
7468
|
+
};
|
|
7469
|
+
return Proxy.revocable(Object.create(null), handler).proxy;
|
|
7470
|
+
}
|
|
7471
|
+
function keysFromSelector(selector, opts) {
|
|
7472
|
+
const {
|
|
7473
|
+
[PATH_KEY]: path
|
|
7474
|
+
} = selector(createProxy());
|
|
7475
|
+
return path.join(opts?.keySeparator ?? '.');
|
|
7476
|
+
}
|
|
7477
|
+
|
|
7478
|
+
const checkedLoadedFor = {};
|
|
7479
|
+
const shouldHandleAsObject = res => !isString(res) && typeof res !== 'boolean' && typeof res !== 'number';
|
|
7480
|
+
class Translator extends EventEmitter {
|
|
7481
|
+
constructor(services, options = {}) {
|
|
7482
|
+
super();
|
|
7483
|
+
copy(['resourceStore', 'languageUtils', 'pluralResolver', 'interpolator', 'backendConnector', 'i18nFormat', 'utils'], services, this);
|
|
7484
|
+
this.options = options;
|
|
7485
|
+
if (this.options.keySeparator === undefined) {
|
|
7486
|
+
this.options.keySeparator = '.';
|
|
7487
|
+
}
|
|
7488
|
+
this.logger = baseLogger.create('translator');
|
|
7489
|
+
}
|
|
7490
|
+
changeLanguage(lng) {
|
|
7491
|
+
if (lng) this.language = lng;
|
|
7492
|
+
}
|
|
7493
|
+
exists(key, o = {
|
|
7494
|
+
interpolation: {}
|
|
7495
|
+
}) {
|
|
7496
|
+
const opt = {
|
|
7497
|
+
...o
|
|
7498
|
+
};
|
|
7499
|
+
if (key == null) return false;
|
|
7500
|
+
const resolved = this.resolve(key, opt);
|
|
7501
|
+
if (resolved?.res === undefined) return false;
|
|
7502
|
+
const isObject = shouldHandleAsObject(resolved.res);
|
|
7503
|
+
if (opt.returnObjects === false && isObject) {
|
|
7504
|
+
return false;
|
|
7505
|
+
}
|
|
7506
|
+
return true;
|
|
7507
|
+
}
|
|
7508
|
+
extractFromKey(key, opt) {
|
|
7509
|
+
let nsSeparator = opt.nsSeparator !== undefined ? opt.nsSeparator : this.options.nsSeparator;
|
|
7510
|
+
if (nsSeparator === undefined) nsSeparator = ':';
|
|
7511
|
+
const keySeparator = opt.keySeparator !== undefined ? opt.keySeparator : this.options.keySeparator;
|
|
7512
|
+
let namespaces = opt.ns || this.options.defaultNS || [];
|
|
7513
|
+
const wouldCheckForNsInKey = nsSeparator && key.indexOf(nsSeparator) > -1;
|
|
7514
|
+
const seemsNaturalLanguage = !this.options.userDefinedKeySeparator && !opt.keySeparator && !this.options.userDefinedNsSeparator && !opt.nsSeparator && !looksLikeObjectPath(key, nsSeparator, keySeparator);
|
|
7515
|
+
if (wouldCheckForNsInKey && !seemsNaturalLanguage) {
|
|
7516
|
+
const m = key.match(this.interpolator.nestingRegexp);
|
|
7517
|
+
if (m && m.length > 0) {
|
|
7518
|
+
return {
|
|
7519
|
+
key,
|
|
7520
|
+
namespaces: isString(namespaces) ? [namespaces] : namespaces
|
|
7521
|
+
};
|
|
7522
|
+
}
|
|
7523
|
+
const parts = key.split(nsSeparator);
|
|
7524
|
+
if (nsSeparator !== keySeparator || nsSeparator === keySeparator && this.options.ns.indexOf(parts[0]) > -1) namespaces = parts.shift();
|
|
7525
|
+
key = parts.join(keySeparator);
|
|
7526
|
+
}
|
|
7527
|
+
return {
|
|
7528
|
+
key,
|
|
7529
|
+
namespaces: isString(namespaces) ? [namespaces] : namespaces
|
|
7530
|
+
};
|
|
7531
|
+
}
|
|
7532
|
+
translate(keys, o, lastKey) {
|
|
7533
|
+
let opt = typeof o === 'object' ? {
|
|
7534
|
+
...o
|
|
7535
|
+
} : o;
|
|
7536
|
+
if (typeof opt !== 'object' && this.options.overloadTranslationOptionHandler) {
|
|
7537
|
+
opt = this.options.overloadTranslationOptionHandler(arguments);
|
|
7538
|
+
}
|
|
7539
|
+
if (typeof opt === 'object') opt = {
|
|
7540
|
+
...opt
|
|
7541
|
+
};
|
|
7542
|
+
if (!opt) opt = {};
|
|
7543
|
+
if (keys == null) return '';
|
|
7544
|
+
if (typeof keys === 'function') keys = keysFromSelector(keys, {
|
|
7545
|
+
...this.options,
|
|
7546
|
+
...opt
|
|
7547
|
+
});
|
|
7548
|
+
if (!Array.isArray(keys)) keys = [String(keys)];
|
|
7549
|
+
const returnDetails = opt.returnDetails !== undefined ? opt.returnDetails : this.options.returnDetails;
|
|
7550
|
+
const keySeparator = opt.keySeparator !== undefined ? opt.keySeparator : this.options.keySeparator;
|
|
7551
|
+
const {
|
|
7552
|
+
key,
|
|
7553
|
+
namespaces
|
|
7554
|
+
} = this.extractFromKey(keys[keys.length - 1], opt);
|
|
7555
|
+
const namespace = namespaces[namespaces.length - 1];
|
|
7556
|
+
let nsSeparator = opt.nsSeparator !== undefined ? opt.nsSeparator : this.options.nsSeparator;
|
|
7557
|
+
if (nsSeparator === undefined) nsSeparator = ':';
|
|
7558
|
+
const lng = opt.lng || this.language;
|
|
7559
|
+
const appendNamespaceToCIMode = opt.appendNamespaceToCIMode || this.options.appendNamespaceToCIMode;
|
|
7560
|
+
if (lng?.toLowerCase() === 'cimode') {
|
|
7561
|
+
if (appendNamespaceToCIMode) {
|
|
7562
|
+
if (returnDetails) {
|
|
7563
|
+
return {
|
|
7564
|
+
res: `${namespace}${nsSeparator}${key}`,
|
|
7565
|
+
usedKey: key,
|
|
7566
|
+
exactUsedKey: key,
|
|
7567
|
+
usedLng: lng,
|
|
7568
|
+
usedNS: namespace,
|
|
7569
|
+
usedParams: this.getUsedParamsDetails(opt)
|
|
7570
|
+
};
|
|
7571
|
+
}
|
|
7572
|
+
return `${namespace}${nsSeparator}${key}`;
|
|
7573
|
+
}
|
|
7574
|
+
if (returnDetails) {
|
|
7575
|
+
return {
|
|
7576
|
+
res: key,
|
|
7577
|
+
usedKey: key,
|
|
7578
|
+
exactUsedKey: key,
|
|
7579
|
+
usedLng: lng,
|
|
7580
|
+
usedNS: namespace,
|
|
7581
|
+
usedParams: this.getUsedParamsDetails(opt)
|
|
7582
|
+
};
|
|
7583
|
+
}
|
|
7584
|
+
return key;
|
|
7585
|
+
}
|
|
7586
|
+
const resolved = this.resolve(keys, opt);
|
|
7587
|
+
let res = resolved?.res;
|
|
7588
|
+
const resUsedKey = resolved?.usedKey || key;
|
|
7589
|
+
const resExactUsedKey = resolved?.exactUsedKey || key;
|
|
7590
|
+
const noObject = ['[object Number]', '[object Function]', '[object RegExp]'];
|
|
7591
|
+
const joinArrays = opt.joinArrays !== undefined ? opt.joinArrays : this.options.joinArrays;
|
|
7592
|
+
const handleAsObjectInI18nFormat = !this.i18nFormat || this.i18nFormat.handleAsObject;
|
|
7593
|
+
const needsPluralHandling = opt.count !== undefined && !isString(opt.count);
|
|
7594
|
+
const hasDefaultValue = Translator.hasDefaultValue(opt);
|
|
7595
|
+
const defaultValueSuffix = needsPluralHandling ? this.pluralResolver.getSuffix(lng, opt.count, opt) : '';
|
|
7596
|
+
const defaultValueSuffixOrdinalFallback = opt.ordinal && needsPluralHandling ? this.pluralResolver.getSuffix(lng, opt.count, {
|
|
7597
|
+
ordinal: false
|
|
7598
|
+
}) : '';
|
|
7599
|
+
const needsZeroSuffixLookup = needsPluralHandling && !opt.ordinal && opt.count === 0;
|
|
7600
|
+
const defaultValue = needsZeroSuffixLookup && opt[`defaultValue${this.options.pluralSeparator}zero`] || opt[`defaultValue${defaultValueSuffix}`] || opt[`defaultValue${defaultValueSuffixOrdinalFallback}`] || opt.defaultValue;
|
|
7601
|
+
let resForObjHndl = res;
|
|
7602
|
+
if (handleAsObjectInI18nFormat && !res && hasDefaultValue) {
|
|
7603
|
+
resForObjHndl = defaultValue;
|
|
7604
|
+
}
|
|
7605
|
+
const handleAsObject = shouldHandleAsObject(resForObjHndl);
|
|
7606
|
+
const resType = Object.prototype.toString.apply(resForObjHndl);
|
|
7607
|
+
if (handleAsObjectInI18nFormat && resForObjHndl && handleAsObject && noObject.indexOf(resType) < 0 && !(isString(joinArrays) && Array.isArray(resForObjHndl))) {
|
|
7608
|
+
if (!opt.returnObjects && !this.options.returnObjects) {
|
|
7609
|
+
if (!this.options.returnedObjectHandler) {
|
|
7610
|
+
this.logger.warn('accessing an object - but returnObjects options is not enabled!');
|
|
7611
|
+
}
|
|
7612
|
+
const r = this.options.returnedObjectHandler ? this.options.returnedObjectHandler(resUsedKey, resForObjHndl, {
|
|
7613
|
+
...opt,
|
|
7614
|
+
ns: namespaces
|
|
7615
|
+
}) : `key '${key} (${this.language})' returned an object instead of string.`;
|
|
7616
|
+
if (returnDetails) {
|
|
7617
|
+
resolved.res = r;
|
|
7618
|
+
resolved.usedParams = this.getUsedParamsDetails(opt);
|
|
7619
|
+
return resolved;
|
|
7620
|
+
}
|
|
7621
|
+
return r;
|
|
7622
|
+
}
|
|
7623
|
+
if (keySeparator) {
|
|
7624
|
+
const resTypeIsArray = Array.isArray(resForObjHndl);
|
|
7625
|
+
const copy = resTypeIsArray ? [] : {};
|
|
7626
|
+
const newKeyToUse = resTypeIsArray ? resExactUsedKey : resUsedKey;
|
|
7627
|
+
for (const m in resForObjHndl) {
|
|
7628
|
+
if (Object.prototype.hasOwnProperty.call(resForObjHndl, m)) {
|
|
7629
|
+
const deepKey = `${newKeyToUse}${keySeparator}${m}`;
|
|
7630
|
+
if (hasDefaultValue && !res) {
|
|
7631
|
+
copy[m] = this.translate(deepKey, {
|
|
7632
|
+
...opt,
|
|
7633
|
+
defaultValue: shouldHandleAsObject(defaultValue) ? defaultValue[m] : undefined,
|
|
7634
|
+
...{
|
|
7635
|
+
joinArrays: false,
|
|
7636
|
+
ns: namespaces
|
|
7637
|
+
}
|
|
7638
|
+
});
|
|
7639
|
+
} else {
|
|
7640
|
+
copy[m] = this.translate(deepKey, {
|
|
7641
|
+
...opt,
|
|
7642
|
+
...{
|
|
7643
|
+
joinArrays: false,
|
|
7644
|
+
ns: namespaces
|
|
7645
|
+
}
|
|
7646
|
+
});
|
|
7647
|
+
}
|
|
7648
|
+
if (copy[m] === deepKey) copy[m] = resForObjHndl[m];
|
|
7649
|
+
}
|
|
7650
|
+
}
|
|
7651
|
+
res = copy;
|
|
7652
|
+
}
|
|
7653
|
+
} else if (handleAsObjectInI18nFormat && isString(joinArrays) && Array.isArray(res)) {
|
|
7654
|
+
res = res.join(joinArrays);
|
|
7655
|
+
if (res) res = this.extendTranslation(res, keys, opt, lastKey);
|
|
7656
|
+
} else {
|
|
7657
|
+
let usedDefault = false;
|
|
7658
|
+
let usedKey = false;
|
|
7659
|
+
if (!this.isValidLookup(res) && hasDefaultValue) {
|
|
7660
|
+
usedDefault = true;
|
|
7661
|
+
res = defaultValue;
|
|
7662
|
+
}
|
|
7663
|
+
if (!this.isValidLookup(res)) {
|
|
7664
|
+
usedKey = true;
|
|
7665
|
+
res = key;
|
|
7666
|
+
}
|
|
7667
|
+
const missingKeyNoValueFallbackToKey = opt.missingKeyNoValueFallbackToKey || this.options.missingKeyNoValueFallbackToKey;
|
|
7668
|
+
const resForMissing = missingKeyNoValueFallbackToKey && usedKey ? undefined : res;
|
|
7669
|
+
const updateMissing = hasDefaultValue && defaultValue !== res && this.options.updateMissing;
|
|
7670
|
+
if (usedKey || usedDefault || updateMissing) {
|
|
7671
|
+
this.logger.log(updateMissing ? 'updateKey' : 'missingKey', lng, namespace, key, updateMissing ? defaultValue : res);
|
|
7672
|
+
if (keySeparator) {
|
|
7673
|
+
const fk = this.resolve(key, {
|
|
7674
|
+
...opt,
|
|
7675
|
+
keySeparator: false
|
|
7676
|
+
});
|
|
7677
|
+
if (fk && fk.res) this.logger.warn('Seems the loaded translations were in flat JSON format instead of nested. Either set keySeparator: false on init or make sure your translations are published in nested format.');
|
|
7678
|
+
}
|
|
7679
|
+
let lngs = [];
|
|
7680
|
+
const fallbackLngs = this.languageUtils.getFallbackCodes(this.options.fallbackLng, opt.lng || this.language);
|
|
7681
|
+
if (this.options.saveMissingTo === 'fallback' && fallbackLngs && fallbackLngs[0]) {
|
|
7682
|
+
for (let i = 0; i < fallbackLngs.length; i++) {
|
|
7683
|
+
lngs.push(fallbackLngs[i]);
|
|
7684
|
+
}
|
|
7685
|
+
} else if (this.options.saveMissingTo === 'all') {
|
|
7686
|
+
lngs = this.languageUtils.toResolveHierarchy(opt.lng || this.language);
|
|
7687
|
+
} else {
|
|
7688
|
+
lngs.push(opt.lng || this.language);
|
|
7689
|
+
}
|
|
7690
|
+
const send = (l, k, specificDefaultValue) => {
|
|
7691
|
+
const defaultForMissing = hasDefaultValue && specificDefaultValue !== res ? specificDefaultValue : resForMissing;
|
|
7692
|
+
if (this.options.missingKeyHandler) {
|
|
7693
|
+
this.options.missingKeyHandler(l, namespace, k, defaultForMissing, updateMissing, opt);
|
|
7694
|
+
} else if (this.backendConnector?.saveMissing) {
|
|
7695
|
+
this.backendConnector.saveMissing(l, namespace, k, defaultForMissing, updateMissing, opt);
|
|
7696
|
+
}
|
|
7697
|
+
this.emit('missingKey', l, namespace, k, res);
|
|
7698
|
+
};
|
|
7699
|
+
if (this.options.saveMissing) {
|
|
7700
|
+
if (this.options.saveMissingPlurals && needsPluralHandling) {
|
|
7701
|
+
lngs.forEach(language => {
|
|
7702
|
+
const suffixes = this.pluralResolver.getSuffixes(language, opt);
|
|
7703
|
+
if (needsZeroSuffixLookup && opt[`defaultValue${this.options.pluralSeparator}zero`] && suffixes.indexOf(`${this.options.pluralSeparator}zero`) < 0) {
|
|
7704
|
+
suffixes.push(`${this.options.pluralSeparator}zero`);
|
|
7705
|
+
}
|
|
7706
|
+
suffixes.forEach(suffix => {
|
|
7707
|
+
send([language], key + suffix, opt[`defaultValue${suffix}`] || defaultValue);
|
|
7708
|
+
});
|
|
7709
|
+
});
|
|
7710
|
+
} else {
|
|
7711
|
+
send(lngs, key, defaultValue);
|
|
7712
|
+
}
|
|
7713
|
+
}
|
|
7714
|
+
}
|
|
7715
|
+
res = this.extendTranslation(res, keys, opt, resolved, lastKey);
|
|
7716
|
+
if (usedKey && res === key && this.options.appendNamespaceToMissingKey) {
|
|
7717
|
+
res = `${namespace}${nsSeparator}${key}`;
|
|
7718
|
+
}
|
|
7719
|
+
if ((usedKey || usedDefault) && this.options.parseMissingKeyHandler) {
|
|
7720
|
+
res = this.options.parseMissingKeyHandler(this.options.appendNamespaceToMissingKey ? `${namespace}${nsSeparator}${key}` : key, usedDefault ? res : undefined, opt);
|
|
7721
|
+
}
|
|
7722
|
+
}
|
|
7723
|
+
if (returnDetails) {
|
|
7724
|
+
resolved.res = res;
|
|
7725
|
+
resolved.usedParams = this.getUsedParamsDetails(opt);
|
|
7726
|
+
return resolved;
|
|
7727
|
+
}
|
|
7728
|
+
return res;
|
|
7729
|
+
}
|
|
7730
|
+
extendTranslation(res, key, opt, resolved, lastKey) {
|
|
7731
|
+
if (this.i18nFormat?.parse) {
|
|
7732
|
+
res = this.i18nFormat.parse(res, {
|
|
7733
|
+
...this.options.interpolation.defaultVariables,
|
|
7734
|
+
...opt
|
|
7735
|
+
}, opt.lng || this.language || resolved.usedLng, resolved.usedNS, resolved.usedKey, {
|
|
7736
|
+
resolved
|
|
7737
|
+
});
|
|
7738
|
+
} else if (!opt.skipInterpolation) {
|
|
7739
|
+
if (opt.interpolation) this.interpolator.init({
|
|
7740
|
+
...opt,
|
|
7741
|
+
...{
|
|
7742
|
+
interpolation: {
|
|
7743
|
+
...this.options.interpolation,
|
|
7744
|
+
...opt.interpolation
|
|
7745
|
+
}
|
|
7746
|
+
}
|
|
7747
|
+
});
|
|
7748
|
+
const skipOnVariables = isString(res) && (opt?.interpolation?.skipOnVariables !== undefined ? opt.interpolation.skipOnVariables : this.options.interpolation.skipOnVariables);
|
|
7749
|
+
let nestBef;
|
|
7750
|
+
if (skipOnVariables) {
|
|
7751
|
+
const nb = res.match(this.interpolator.nestingRegexp);
|
|
7752
|
+
nestBef = nb && nb.length;
|
|
7753
|
+
}
|
|
7754
|
+
let data = opt.replace && !isString(opt.replace) ? opt.replace : opt;
|
|
7755
|
+
if (this.options.interpolation.defaultVariables) data = {
|
|
7756
|
+
...this.options.interpolation.defaultVariables,
|
|
7757
|
+
...data
|
|
7758
|
+
};
|
|
7759
|
+
res = this.interpolator.interpolate(res, data, opt.lng || this.language || resolved.usedLng, opt);
|
|
7760
|
+
if (skipOnVariables) {
|
|
7761
|
+
const na = res.match(this.interpolator.nestingRegexp);
|
|
7762
|
+
const nestAft = na && na.length;
|
|
7763
|
+
if (nestBef < nestAft) opt.nest = false;
|
|
7764
|
+
}
|
|
7765
|
+
if (!opt.lng && resolved && resolved.res) opt.lng = this.language || resolved.usedLng;
|
|
7766
|
+
if (opt.nest !== false) res = this.interpolator.nest(res, (...args) => {
|
|
7767
|
+
if (lastKey?.[0] === args[0] && !opt.context) {
|
|
7768
|
+
this.logger.warn(`It seems you are nesting recursively key: ${args[0]} in key: ${key[0]}`);
|
|
7769
|
+
return null;
|
|
7770
|
+
}
|
|
7771
|
+
return this.translate(...args, key);
|
|
7772
|
+
}, opt);
|
|
7773
|
+
if (opt.interpolation) this.interpolator.reset();
|
|
7774
|
+
}
|
|
7775
|
+
const postProcess = opt.postProcess || this.options.postProcess;
|
|
7776
|
+
const postProcessorNames = isString(postProcess) ? [postProcess] : postProcess;
|
|
7777
|
+
if (res != null && postProcessorNames?.length && opt.applyPostProcessor !== false) {
|
|
7778
|
+
res = postProcessor.handle(postProcessorNames, res, key, this.options && this.options.postProcessPassResolved ? {
|
|
7779
|
+
i18nResolved: {
|
|
7780
|
+
...resolved,
|
|
7781
|
+
usedParams: this.getUsedParamsDetails(opt)
|
|
7782
|
+
},
|
|
7783
|
+
...opt
|
|
7784
|
+
} : opt, this);
|
|
7785
|
+
}
|
|
7786
|
+
return res;
|
|
7787
|
+
}
|
|
7788
|
+
resolve(keys, opt = {}) {
|
|
7789
|
+
let found;
|
|
7790
|
+
let usedKey;
|
|
7791
|
+
let exactUsedKey;
|
|
7792
|
+
let usedLng;
|
|
7793
|
+
let usedNS;
|
|
7794
|
+
if (isString(keys)) keys = [keys];
|
|
7795
|
+
keys.forEach(k => {
|
|
7796
|
+
if (this.isValidLookup(found)) return;
|
|
7797
|
+
const extracted = this.extractFromKey(k, opt);
|
|
7798
|
+
const key = extracted.key;
|
|
7799
|
+
usedKey = key;
|
|
7800
|
+
let namespaces = extracted.namespaces;
|
|
7801
|
+
if (this.options.fallbackNS) namespaces = namespaces.concat(this.options.fallbackNS);
|
|
7802
|
+
const needsPluralHandling = opt.count !== undefined && !isString(opt.count);
|
|
7803
|
+
const needsZeroSuffixLookup = needsPluralHandling && !opt.ordinal && opt.count === 0;
|
|
7804
|
+
const needsContextHandling = opt.context !== undefined && (isString(opt.context) || typeof opt.context === 'number') && opt.context !== '';
|
|
7805
|
+
const codes = opt.lngs ? opt.lngs : this.languageUtils.toResolveHierarchy(opt.lng || this.language, opt.fallbackLng);
|
|
7806
|
+
namespaces.forEach(ns => {
|
|
7807
|
+
if (this.isValidLookup(found)) return;
|
|
7808
|
+
usedNS = ns;
|
|
7809
|
+
if (!checkedLoadedFor[`${codes[0]}-${ns}`] && this.utils?.hasLoadedNamespace && !this.utils?.hasLoadedNamespace(usedNS)) {
|
|
7810
|
+
checkedLoadedFor[`${codes[0]}-${ns}`] = true;
|
|
7811
|
+
this.logger.warn(`key "${usedKey}" for languages "${codes.join(', ')}" won't get resolved as namespace "${usedNS}" was not yet loaded`, 'This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!');
|
|
7812
|
+
}
|
|
7813
|
+
codes.forEach(code => {
|
|
7814
|
+
if (this.isValidLookup(found)) return;
|
|
7815
|
+
usedLng = code;
|
|
7816
|
+
const finalKeys = [key];
|
|
7817
|
+
if (this.i18nFormat?.addLookupKeys) {
|
|
7818
|
+
this.i18nFormat.addLookupKeys(finalKeys, key, code, ns, opt);
|
|
7819
|
+
} else {
|
|
7820
|
+
let pluralSuffix;
|
|
7821
|
+
if (needsPluralHandling) pluralSuffix = this.pluralResolver.getSuffix(code, opt.count, opt);
|
|
7822
|
+
const zeroSuffix = `${this.options.pluralSeparator}zero`;
|
|
7823
|
+
const ordinalPrefix = `${this.options.pluralSeparator}ordinal${this.options.pluralSeparator}`;
|
|
7824
|
+
if (needsPluralHandling) {
|
|
7825
|
+
if (opt.ordinal && pluralSuffix.indexOf(ordinalPrefix) === 0) {
|
|
7826
|
+
finalKeys.push(key + pluralSuffix.replace(ordinalPrefix, this.options.pluralSeparator));
|
|
7827
|
+
}
|
|
7828
|
+
finalKeys.push(key + pluralSuffix);
|
|
7829
|
+
if (needsZeroSuffixLookup) {
|
|
7830
|
+
finalKeys.push(key + zeroSuffix);
|
|
7831
|
+
}
|
|
7832
|
+
}
|
|
7833
|
+
if (needsContextHandling) {
|
|
7834
|
+
const contextKey = `${key}${this.options.contextSeparator || '_'}${opt.context}`;
|
|
7835
|
+
finalKeys.push(contextKey);
|
|
7836
|
+
if (needsPluralHandling) {
|
|
7837
|
+
if (opt.ordinal && pluralSuffix.indexOf(ordinalPrefix) === 0) {
|
|
7838
|
+
finalKeys.push(contextKey + pluralSuffix.replace(ordinalPrefix, this.options.pluralSeparator));
|
|
7839
|
+
}
|
|
7840
|
+
finalKeys.push(contextKey + pluralSuffix);
|
|
7841
|
+
if (needsZeroSuffixLookup) {
|
|
7842
|
+
finalKeys.push(contextKey + zeroSuffix);
|
|
7843
|
+
}
|
|
7844
|
+
}
|
|
7845
|
+
}
|
|
7846
|
+
}
|
|
7847
|
+
let possibleKey;
|
|
7848
|
+
while (possibleKey = finalKeys.pop()) {
|
|
7849
|
+
if (!this.isValidLookup(found)) {
|
|
7850
|
+
exactUsedKey = possibleKey;
|
|
7851
|
+
found = this.getResource(code, ns, possibleKey, opt);
|
|
7852
|
+
}
|
|
7853
|
+
}
|
|
7854
|
+
});
|
|
7855
|
+
});
|
|
7856
|
+
});
|
|
7857
|
+
return {
|
|
7858
|
+
res: found,
|
|
7859
|
+
usedKey,
|
|
7860
|
+
exactUsedKey,
|
|
7861
|
+
usedLng,
|
|
7862
|
+
usedNS
|
|
7863
|
+
};
|
|
7864
|
+
}
|
|
7865
|
+
isValidLookup(res) {
|
|
7866
|
+
return res !== undefined && !(!this.options.returnNull && res === null) && !(!this.options.returnEmptyString && res === '');
|
|
7867
|
+
}
|
|
7868
|
+
getResource(code, ns, key, options = {}) {
|
|
7869
|
+
if (this.i18nFormat?.getResource) return this.i18nFormat.getResource(code, ns, key, options);
|
|
7870
|
+
return this.resourceStore.getResource(code, ns, key, options);
|
|
7871
|
+
}
|
|
7872
|
+
getUsedParamsDetails(options = {}) {
|
|
7873
|
+
const optionsKeys = ['defaultValue', 'ordinal', 'context', 'replace', 'lng', 'lngs', 'fallbackLng', 'ns', 'keySeparator', 'nsSeparator', 'returnObjects', 'returnDetails', 'joinArrays', 'postProcess', 'interpolation'];
|
|
7874
|
+
const useOptionsReplaceForData = options.replace && !isString(options.replace);
|
|
7875
|
+
let data = useOptionsReplaceForData ? options.replace : options;
|
|
7876
|
+
if (useOptionsReplaceForData && typeof options.count !== 'undefined') {
|
|
7877
|
+
data.count = options.count;
|
|
7878
|
+
}
|
|
7879
|
+
if (this.options.interpolation.defaultVariables) {
|
|
7880
|
+
data = {
|
|
7881
|
+
...this.options.interpolation.defaultVariables,
|
|
7882
|
+
...data
|
|
7883
|
+
};
|
|
7884
|
+
}
|
|
7885
|
+
if (!useOptionsReplaceForData) {
|
|
7886
|
+
data = {
|
|
7887
|
+
...data
|
|
7888
|
+
};
|
|
7889
|
+
for (const key of optionsKeys) {
|
|
7890
|
+
delete data[key];
|
|
7891
|
+
}
|
|
7892
|
+
}
|
|
7893
|
+
return data;
|
|
7894
|
+
}
|
|
7895
|
+
static hasDefaultValue(options) {
|
|
7896
|
+
const prefix = 'defaultValue';
|
|
7897
|
+
for (const option in options) {
|
|
7898
|
+
if (Object.prototype.hasOwnProperty.call(options, option) && prefix === option.substring(0, prefix.length) && undefined !== options[option]) {
|
|
7899
|
+
return true;
|
|
7900
|
+
}
|
|
7901
|
+
}
|
|
7902
|
+
return false;
|
|
7903
|
+
}
|
|
7904
|
+
}
|
|
7905
|
+
|
|
7906
|
+
class LanguageUtil {
|
|
7907
|
+
constructor(options) {
|
|
7908
|
+
this.options = options;
|
|
7909
|
+
this.supportedLngs = this.options.supportedLngs || false;
|
|
7910
|
+
this.logger = baseLogger.create('languageUtils');
|
|
7911
|
+
}
|
|
7912
|
+
getScriptPartFromCode(code) {
|
|
7913
|
+
code = getCleanedCode(code);
|
|
7914
|
+
if (!code || code.indexOf('-') < 0) return null;
|
|
7915
|
+
const p = code.split('-');
|
|
7916
|
+
if (p.length === 2) return null;
|
|
7917
|
+
p.pop();
|
|
7918
|
+
if (p[p.length - 1].toLowerCase() === 'x') return null;
|
|
7919
|
+
return this.formatLanguageCode(p.join('-'));
|
|
7920
|
+
}
|
|
7921
|
+
getLanguagePartFromCode(code) {
|
|
7922
|
+
code = getCleanedCode(code);
|
|
7923
|
+
if (!code || code.indexOf('-') < 0) return code;
|
|
7924
|
+
const p = code.split('-');
|
|
7925
|
+
return this.formatLanguageCode(p[0]);
|
|
7926
|
+
}
|
|
7927
|
+
formatLanguageCode(code) {
|
|
7928
|
+
if (isString(code) && code.indexOf('-') > -1) {
|
|
7929
|
+
let formattedCode;
|
|
7930
|
+
try {
|
|
7931
|
+
formattedCode = Intl.getCanonicalLocales(code)[0];
|
|
7932
|
+
} catch (e) {}
|
|
7933
|
+
if (formattedCode && this.options.lowerCaseLng) {
|
|
7934
|
+
formattedCode = formattedCode.toLowerCase();
|
|
7935
|
+
}
|
|
7936
|
+
if (formattedCode) return formattedCode;
|
|
7937
|
+
if (this.options.lowerCaseLng) {
|
|
7938
|
+
return code.toLowerCase();
|
|
7939
|
+
}
|
|
7940
|
+
return code;
|
|
7941
|
+
}
|
|
7942
|
+
return this.options.cleanCode || this.options.lowerCaseLng ? code.toLowerCase() : code;
|
|
7943
|
+
}
|
|
7944
|
+
isSupportedCode(code) {
|
|
7945
|
+
if (this.options.load === 'languageOnly' || this.options.nonExplicitSupportedLngs) {
|
|
7946
|
+
code = this.getLanguagePartFromCode(code);
|
|
7947
|
+
}
|
|
7948
|
+
return !this.supportedLngs || !this.supportedLngs.length || this.supportedLngs.indexOf(code) > -1;
|
|
7949
|
+
}
|
|
7950
|
+
getBestMatchFromCodes(codes) {
|
|
7951
|
+
if (!codes) return null;
|
|
7952
|
+
let found;
|
|
7953
|
+
codes.forEach(code => {
|
|
7954
|
+
if (found) return;
|
|
7955
|
+
const cleanedLng = this.formatLanguageCode(code);
|
|
7956
|
+
if (!this.options.supportedLngs || this.isSupportedCode(cleanedLng)) found = cleanedLng;
|
|
7957
|
+
});
|
|
7958
|
+
if (!found && this.options.supportedLngs) {
|
|
7959
|
+
codes.forEach(code => {
|
|
7960
|
+
if (found) return;
|
|
7961
|
+
const lngScOnly = this.getScriptPartFromCode(code);
|
|
7962
|
+
if (this.isSupportedCode(lngScOnly)) return found = lngScOnly;
|
|
7963
|
+
const lngOnly = this.getLanguagePartFromCode(code);
|
|
7964
|
+
if (this.isSupportedCode(lngOnly)) return found = lngOnly;
|
|
7965
|
+
found = this.options.supportedLngs.find(supportedLng => {
|
|
7966
|
+
if (supportedLng === lngOnly) return supportedLng;
|
|
7967
|
+
if (supportedLng.indexOf('-') < 0 && lngOnly.indexOf('-') < 0) return;
|
|
7968
|
+
if (supportedLng.indexOf('-') > 0 && lngOnly.indexOf('-') < 0 && supportedLng.substring(0, supportedLng.indexOf('-')) === lngOnly) return supportedLng;
|
|
7969
|
+
if (supportedLng.indexOf(lngOnly) === 0 && lngOnly.length > 1) return supportedLng;
|
|
7970
|
+
});
|
|
7971
|
+
});
|
|
7972
|
+
}
|
|
7973
|
+
if (!found) found = this.getFallbackCodes(this.options.fallbackLng)[0];
|
|
7974
|
+
return found;
|
|
7975
|
+
}
|
|
7976
|
+
getFallbackCodes(fallbacks, code) {
|
|
7977
|
+
if (!fallbacks) return [];
|
|
7978
|
+
if (typeof fallbacks === 'function') fallbacks = fallbacks(code);
|
|
7979
|
+
if (isString(fallbacks)) fallbacks = [fallbacks];
|
|
7980
|
+
if (Array.isArray(fallbacks)) return fallbacks;
|
|
7981
|
+
if (!code) return fallbacks.default || [];
|
|
7982
|
+
let found = fallbacks[code];
|
|
7983
|
+
if (!found) found = fallbacks[this.getScriptPartFromCode(code)];
|
|
7984
|
+
if (!found) found = fallbacks[this.formatLanguageCode(code)];
|
|
7985
|
+
if (!found) found = fallbacks[this.getLanguagePartFromCode(code)];
|
|
7986
|
+
if (!found) found = fallbacks.default;
|
|
7987
|
+
return found || [];
|
|
7988
|
+
}
|
|
7989
|
+
toResolveHierarchy(code, fallbackCode) {
|
|
7990
|
+
const fallbackCodes = this.getFallbackCodes((fallbackCode === false ? [] : fallbackCode) || this.options.fallbackLng || [], code);
|
|
7991
|
+
const codes = [];
|
|
7992
|
+
const addCode = c => {
|
|
7993
|
+
if (!c) return;
|
|
7994
|
+
if (this.isSupportedCode(c)) {
|
|
7995
|
+
codes.push(c);
|
|
7996
|
+
} else {
|
|
7997
|
+
this.logger.warn(`rejecting language code not found in supportedLngs: ${c}`);
|
|
7998
|
+
}
|
|
7999
|
+
};
|
|
8000
|
+
if (isString(code) && (code.indexOf('-') > -1 || code.indexOf('_') > -1)) {
|
|
8001
|
+
if (this.options.load !== 'languageOnly') addCode(this.formatLanguageCode(code));
|
|
8002
|
+
if (this.options.load !== 'languageOnly' && this.options.load !== 'currentOnly') addCode(this.getScriptPartFromCode(code));
|
|
8003
|
+
if (this.options.load !== 'currentOnly') addCode(this.getLanguagePartFromCode(code));
|
|
8004
|
+
} else if (isString(code)) {
|
|
8005
|
+
addCode(this.formatLanguageCode(code));
|
|
8006
|
+
}
|
|
8007
|
+
fallbackCodes.forEach(fc => {
|
|
8008
|
+
if (codes.indexOf(fc) < 0) addCode(this.formatLanguageCode(fc));
|
|
8009
|
+
});
|
|
8010
|
+
return codes;
|
|
8011
|
+
}
|
|
8012
|
+
}
|
|
8013
|
+
|
|
8014
|
+
const suffixesOrder = {
|
|
8015
|
+
zero: 0,
|
|
8016
|
+
one: 1,
|
|
8017
|
+
two: 2,
|
|
8018
|
+
few: 3,
|
|
8019
|
+
many: 4,
|
|
8020
|
+
other: 5
|
|
8021
|
+
};
|
|
8022
|
+
const dummyRule = {
|
|
8023
|
+
select: count => count === 1 ? 'one' : 'other',
|
|
8024
|
+
resolvedOptions: () => ({
|
|
8025
|
+
pluralCategories: ['one', 'other']
|
|
8026
|
+
})
|
|
8027
|
+
};
|
|
8028
|
+
class PluralResolver {
|
|
8029
|
+
constructor(languageUtils, options = {}) {
|
|
8030
|
+
this.languageUtils = languageUtils;
|
|
8031
|
+
this.options = options;
|
|
8032
|
+
this.logger = baseLogger.create('pluralResolver');
|
|
8033
|
+
this.pluralRulesCache = {};
|
|
8034
|
+
}
|
|
8035
|
+
clearCache() {
|
|
8036
|
+
this.pluralRulesCache = {};
|
|
8037
|
+
}
|
|
8038
|
+
getRule(code, options = {}) {
|
|
8039
|
+
const cleanedCode = getCleanedCode(code === 'dev' ? 'en' : code);
|
|
8040
|
+
const type = options.ordinal ? 'ordinal' : 'cardinal';
|
|
8041
|
+
const cacheKey = JSON.stringify({
|
|
8042
|
+
cleanedCode,
|
|
8043
|
+
type
|
|
8044
|
+
});
|
|
8045
|
+
if (cacheKey in this.pluralRulesCache) {
|
|
8046
|
+
return this.pluralRulesCache[cacheKey];
|
|
8047
|
+
}
|
|
8048
|
+
let rule;
|
|
8049
|
+
try {
|
|
8050
|
+
rule = new Intl.PluralRules(cleanedCode, {
|
|
8051
|
+
type
|
|
8052
|
+
});
|
|
8053
|
+
} catch (err) {
|
|
8054
|
+
if (!Intl) {
|
|
8055
|
+
this.logger.error('No Intl support, please use an Intl polyfill!');
|
|
8056
|
+
return dummyRule;
|
|
8057
|
+
}
|
|
8058
|
+
if (!code.match(/-|_/)) return dummyRule;
|
|
8059
|
+
const lngPart = this.languageUtils.getLanguagePartFromCode(code);
|
|
8060
|
+
rule = this.getRule(lngPart, options);
|
|
8061
|
+
}
|
|
8062
|
+
this.pluralRulesCache[cacheKey] = rule;
|
|
8063
|
+
return rule;
|
|
8064
|
+
}
|
|
8065
|
+
needsPlural(code, options = {}) {
|
|
8066
|
+
let rule = this.getRule(code, options);
|
|
8067
|
+
if (!rule) rule = this.getRule('dev', options);
|
|
8068
|
+
return rule?.resolvedOptions().pluralCategories.length > 1;
|
|
8069
|
+
}
|
|
8070
|
+
getPluralFormsOfKey(code, key, options = {}) {
|
|
8071
|
+
return this.getSuffixes(code, options).map(suffix => `${key}${suffix}`);
|
|
8072
|
+
}
|
|
8073
|
+
getSuffixes(code, options = {}) {
|
|
8074
|
+
let rule = this.getRule(code, options);
|
|
8075
|
+
if (!rule) rule = this.getRule('dev', options);
|
|
8076
|
+
if (!rule) return [];
|
|
8077
|
+
return rule.resolvedOptions().pluralCategories.sort((pluralCategory1, pluralCategory2) => suffixesOrder[pluralCategory1] - suffixesOrder[pluralCategory2]).map(pluralCategory => `${this.options.prepend}${options.ordinal ? `ordinal${this.options.prepend}` : ''}${pluralCategory}`);
|
|
8078
|
+
}
|
|
8079
|
+
getSuffix(code, count, options = {}) {
|
|
8080
|
+
const rule = this.getRule(code, options);
|
|
8081
|
+
if (rule) {
|
|
8082
|
+
return `${this.options.prepend}${options.ordinal ? `ordinal${this.options.prepend}` : ''}${rule.select(count)}`;
|
|
8083
|
+
}
|
|
8084
|
+
this.logger.warn(`no plural rule found for: ${code}`);
|
|
8085
|
+
return this.getSuffix('dev', count, options);
|
|
8086
|
+
}
|
|
8087
|
+
}
|
|
8088
|
+
|
|
8089
|
+
const deepFindWithDefaults = (data, defaultData, key, keySeparator = '.', ignoreJSONStructure = true) => {
|
|
8090
|
+
let path = getPathWithDefaults(data, defaultData, key);
|
|
8091
|
+
if (!path && ignoreJSONStructure && isString(key)) {
|
|
8092
|
+
path = deepFind(data, key, keySeparator);
|
|
8093
|
+
if (path === undefined) path = deepFind(defaultData, key, keySeparator);
|
|
8094
|
+
}
|
|
8095
|
+
return path;
|
|
8096
|
+
};
|
|
8097
|
+
const regexSafe = val => val.replace(/\$/g, '$$$$');
|
|
8098
|
+
class Interpolator {
|
|
8099
|
+
constructor(options = {}) {
|
|
8100
|
+
this.logger = baseLogger.create('interpolator');
|
|
8101
|
+
this.options = options;
|
|
8102
|
+
this.format = options?.interpolation?.format || (value => value);
|
|
8103
|
+
this.init(options);
|
|
8104
|
+
}
|
|
8105
|
+
init(options = {}) {
|
|
8106
|
+
if (!options.interpolation) options.interpolation = {
|
|
8107
|
+
escapeValue: true
|
|
8108
|
+
};
|
|
8109
|
+
const {
|
|
8110
|
+
escape: escape$1,
|
|
8111
|
+
escapeValue,
|
|
8112
|
+
useRawValueToEscape,
|
|
8113
|
+
prefix,
|
|
8114
|
+
prefixEscaped,
|
|
8115
|
+
suffix,
|
|
8116
|
+
suffixEscaped,
|
|
8117
|
+
formatSeparator,
|
|
8118
|
+
unescapeSuffix,
|
|
8119
|
+
unescapePrefix,
|
|
8120
|
+
nestingPrefix,
|
|
8121
|
+
nestingPrefixEscaped,
|
|
8122
|
+
nestingSuffix,
|
|
8123
|
+
nestingSuffixEscaped,
|
|
8124
|
+
nestingOptionsSeparator,
|
|
8125
|
+
maxReplaces,
|
|
8126
|
+
alwaysFormat
|
|
8127
|
+
} = options.interpolation;
|
|
8128
|
+
this.escape = escape$1 !== undefined ? escape$1 : escape;
|
|
8129
|
+
this.escapeValue = escapeValue !== undefined ? escapeValue : true;
|
|
8130
|
+
this.useRawValueToEscape = useRawValueToEscape !== undefined ? useRawValueToEscape : false;
|
|
8131
|
+
this.prefix = prefix ? regexEscape(prefix) : prefixEscaped || '{{';
|
|
8132
|
+
this.suffix = suffix ? regexEscape(suffix) : suffixEscaped || '}}';
|
|
8133
|
+
this.formatSeparator = formatSeparator || ',';
|
|
8134
|
+
this.unescapePrefix = unescapeSuffix ? '' : unescapePrefix || '-';
|
|
8135
|
+
this.unescapeSuffix = this.unescapePrefix ? '' : unescapeSuffix || '';
|
|
8136
|
+
this.nestingPrefix = nestingPrefix ? regexEscape(nestingPrefix) : nestingPrefixEscaped || regexEscape('$t(');
|
|
8137
|
+
this.nestingSuffix = nestingSuffix ? regexEscape(nestingSuffix) : nestingSuffixEscaped || regexEscape(')');
|
|
8138
|
+
this.nestingOptionsSeparator = nestingOptionsSeparator || ',';
|
|
8139
|
+
this.maxReplaces = maxReplaces || 1000;
|
|
8140
|
+
this.alwaysFormat = alwaysFormat !== undefined ? alwaysFormat : false;
|
|
8141
|
+
this.resetRegExp();
|
|
8142
|
+
}
|
|
8143
|
+
reset() {
|
|
8144
|
+
if (this.options) this.init(this.options);
|
|
8145
|
+
}
|
|
8146
|
+
resetRegExp() {
|
|
8147
|
+
const getOrResetRegExp = (existingRegExp, pattern) => {
|
|
8148
|
+
if (existingRegExp?.source === pattern) {
|
|
8149
|
+
existingRegExp.lastIndex = 0;
|
|
8150
|
+
return existingRegExp;
|
|
8151
|
+
}
|
|
8152
|
+
return new RegExp(pattern, 'g');
|
|
8153
|
+
};
|
|
8154
|
+
this.regexp = getOrResetRegExp(this.regexp, `${this.prefix}(.+?)${this.suffix}`);
|
|
8155
|
+
this.regexpUnescape = getOrResetRegExp(this.regexpUnescape, `${this.prefix}${this.unescapePrefix}(.+?)${this.unescapeSuffix}${this.suffix}`);
|
|
8156
|
+
this.nestingRegexp = getOrResetRegExp(this.nestingRegexp, `${this.nestingPrefix}((?:[^()"']+|"[^"]*"|'[^']*'|\\((?:[^()]|"[^"]*"|'[^']*')*\\))*?)${this.nestingSuffix}`);
|
|
8157
|
+
}
|
|
8158
|
+
interpolate(str, data, lng, options) {
|
|
8159
|
+
let match;
|
|
8160
|
+
let value;
|
|
8161
|
+
let replaces;
|
|
8162
|
+
const defaultData = this.options && this.options.interpolation && this.options.interpolation.defaultVariables || {};
|
|
8163
|
+
const handleFormat = key => {
|
|
8164
|
+
if (key.indexOf(this.formatSeparator) < 0) {
|
|
8165
|
+
const path = deepFindWithDefaults(data, defaultData, key, this.options.keySeparator, this.options.ignoreJSONStructure);
|
|
8166
|
+
return this.alwaysFormat ? this.format(path, undefined, lng, {
|
|
8167
|
+
...options,
|
|
8168
|
+
...data,
|
|
8169
|
+
interpolationkey: key
|
|
8170
|
+
}) : path;
|
|
8171
|
+
}
|
|
8172
|
+
const p = key.split(this.formatSeparator);
|
|
8173
|
+
const k = p.shift().trim();
|
|
8174
|
+
const f = p.join(this.formatSeparator).trim();
|
|
8175
|
+
return this.format(deepFindWithDefaults(data, defaultData, k, this.options.keySeparator, this.options.ignoreJSONStructure), f, lng, {
|
|
8176
|
+
...options,
|
|
8177
|
+
...data,
|
|
8178
|
+
interpolationkey: k
|
|
8179
|
+
});
|
|
8180
|
+
};
|
|
8181
|
+
this.resetRegExp();
|
|
8182
|
+
const missingInterpolationHandler = options?.missingInterpolationHandler || this.options.missingInterpolationHandler;
|
|
8183
|
+
const skipOnVariables = options?.interpolation?.skipOnVariables !== undefined ? options.interpolation.skipOnVariables : this.options.interpolation.skipOnVariables;
|
|
8184
|
+
const todos = [{
|
|
8185
|
+
regex: this.regexpUnescape,
|
|
8186
|
+
safeValue: val => regexSafe(val)
|
|
8187
|
+
}, {
|
|
8188
|
+
regex: this.regexp,
|
|
8189
|
+
safeValue: val => this.escapeValue ? regexSafe(this.escape(val)) : regexSafe(val)
|
|
8190
|
+
}];
|
|
8191
|
+
todos.forEach(todo => {
|
|
8192
|
+
replaces = 0;
|
|
8193
|
+
while (match = todo.regex.exec(str)) {
|
|
8194
|
+
const matchedVar = match[1].trim();
|
|
8195
|
+
value = handleFormat(matchedVar);
|
|
8196
|
+
if (value === undefined) {
|
|
8197
|
+
if (typeof missingInterpolationHandler === 'function') {
|
|
8198
|
+
const temp = missingInterpolationHandler(str, match, options);
|
|
8199
|
+
value = isString(temp) ? temp : '';
|
|
8200
|
+
} else if (options && Object.prototype.hasOwnProperty.call(options, matchedVar)) {
|
|
8201
|
+
value = '';
|
|
8202
|
+
} else if (skipOnVariables) {
|
|
8203
|
+
value = match[0];
|
|
8204
|
+
continue;
|
|
8205
|
+
} else {
|
|
8206
|
+
this.logger.warn(`missed to pass in variable ${matchedVar} for interpolating ${str}`);
|
|
8207
|
+
value = '';
|
|
8208
|
+
}
|
|
8209
|
+
} else if (!isString(value) && !this.useRawValueToEscape) {
|
|
8210
|
+
value = makeString(value);
|
|
8211
|
+
}
|
|
8212
|
+
const safeValue = todo.safeValue(value);
|
|
8213
|
+
str = str.replace(match[0], safeValue);
|
|
8214
|
+
if (skipOnVariables) {
|
|
8215
|
+
todo.regex.lastIndex += value.length;
|
|
8216
|
+
todo.regex.lastIndex -= match[0].length;
|
|
8217
|
+
} else {
|
|
8218
|
+
todo.regex.lastIndex = 0;
|
|
8219
|
+
}
|
|
8220
|
+
replaces++;
|
|
8221
|
+
if (replaces >= this.maxReplaces) {
|
|
8222
|
+
break;
|
|
8223
|
+
}
|
|
8224
|
+
}
|
|
8225
|
+
});
|
|
8226
|
+
return str;
|
|
8227
|
+
}
|
|
8228
|
+
nest(str, fc, options = {}) {
|
|
8229
|
+
let match;
|
|
8230
|
+
let value;
|
|
8231
|
+
let clonedOptions;
|
|
8232
|
+
const handleHasOptions = (key, inheritedOptions) => {
|
|
8233
|
+
const sep = this.nestingOptionsSeparator;
|
|
8234
|
+
if (key.indexOf(sep) < 0) return key;
|
|
8235
|
+
const c = key.split(new RegExp(`${sep}[ ]*{`));
|
|
8236
|
+
let optionsString = `{${c[1]}`;
|
|
8237
|
+
key = c[0];
|
|
8238
|
+
optionsString = this.interpolate(optionsString, clonedOptions);
|
|
8239
|
+
const matchedSingleQuotes = optionsString.match(/'/g);
|
|
8240
|
+
const matchedDoubleQuotes = optionsString.match(/"/g);
|
|
8241
|
+
if ((matchedSingleQuotes?.length ?? 0) % 2 === 0 && !matchedDoubleQuotes || matchedDoubleQuotes.length % 2 !== 0) {
|
|
8242
|
+
optionsString = optionsString.replace(/'/g, '"');
|
|
8243
|
+
}
|
|
8244
|
+
try {
|
|
8245
|
+
clonedOptions = JSON.parse(optionsString);
|
|
8246
|
+
if (inheritedOptions) clonedOptions = {
|
|
8247
|
+
...inheritedOptions,
|
|
8248
|
+
...clonedOptions
|
|
8249
|
+
};
|
|
8250
|
+
} catch (e) {
|
|
8251
|
+
this.logger.warn(`failed parsing options string in nesting for key ${key}`, e);
|
|
8252
|
+
return `${key}${sep}${optionsString}`;
|
|
8253
|
+
}
|
|
8254
|
+
if (clonedOptions.defaultValue && clonedOptions.defaultValue.indexOf(this.prefix) > -1) delete clonedOptions.defaultValue;
|
|
8255
|
+
return key;
|
|
8256
|
+
};
|
|
8257
|
+
while (match = this.nestingRegexp.exec(str)) {
|
|
8258
|
+
let formatters = [];
|
|
8259
|
+
clonedOptions = {
|
|
8260
|
+
...options
|
|
8261
|
+
};
|
|
8262
|
+
clonedOptions = clonedOptions.replace && !isString(clonedOptions.replace) ? clonedOptions.replace : clonedOptions;
|
|
8263
|
+
clonedOptions.applyPostProcessor = false;
|
|
8264
|
+
delete clonedOptions.defaultValue;
|
|
8265
|
+
const keyEndIndex = /{.*}/.test(match[1]) ? match[1].lastIndexOf('}') + 1 : match[1].indexOf(this.formatSeparator);
|
|
8266
|
+
if (keyEndIndex !== -1) {
|
|
8267
|
+
formatters = match[1].slice(keyEndIndex).split(this.formatSeparator).map(elem => elem.trim()).filter(Boolean);
|
|
8268
|
+
match[1] = match[1].slice(0, keyEndIndex);
|
|
8269
|
+
}
|
|
8270
|
+
value = fc(handleHasOptions.call(this, match[1].trim(), clonedOptions), clonedOptions);
|
|
8271
|
+
if (value && match[0] === str && !isString(value)) return value;
|
|
8272
|
+
if (!isString(value)) value = makeString(value);
|
|
8273
|
+
if (!value) {
|
|
8274
|
+
this.logger.warn(`missed to resolve ${match[1]} for nesting ${str}`);
|
|
8275
|
+
value = '';
|
|
8276
|
+
}
|
|
8277
|
+
if (formatters.length) {
|
|
8278
|
+
value = formatters.reduce((v, f) => this.format(v, f, options.lng, {
|
|
8279
|
+
...options,
|
|
8280
|
+
interpolationkey: match[1].trim()
|
|
8281
|
+
}), value.trim());
|
|
8282
|
+
}
|
|
8283
|
+
str = str.replace(match[0], value);
|
|
8284
|
+
this.regexp.lastIndex = 0;
|
|
8285
|
+
}
|
|
8286
|
+
return str;
|
|
8287
|
+
}
|
|
8288
|
+
}
|
|
8289
|
+
|
|
8290
|
+
const parseFormatStr = formatStr => {
|
|
8291
|
+
let formatName = formatStr.toLowerCase().trim();
|
|
8292
|
+
const formatOptions = {};
|
|
8293
|
+
if (formatStr.indexOf('(') > -1) {
|
|
8294
|
+
const p = formatStr.split('(');
|
|
8295
|
+
formatName = p[0].toLowerCase().trim();
|
|
8296
|
+
const optStr = p[1].substring(0, p[1].length - 1);
|
|
8297
|
+
if (formatName === 'currency' && optStr.indexOf(':') < 0) {
|
|
8298
|
+
if (!formatOptions.currency) formatOptions.currency = optStr.trim();
|
|
8299
|
+
} else if (formatName === 'relativetime' && optStr.indexOf(':') < 0) {
|
|
8300
|
+
if (!formatOptions.range) formatOptions.range = optStr.trim();
|
|
8301
|
+
} else {
|
|
8302
|
+
const opts = optStr.split(';');
|
|
8303
|
+
opts.forEach(opt => {
|
|
8304
|
+
if (opt) {
|
|
8305
|
+
const [key, ...rest] = opt.split(':');
|
|
8306
|
+
const val = rest.join(':').trim().replace(/^'+|'+$/g, '');
|
|
8307
|
+
const trimmedKey = key.trim();
|
|
8308
|
+
if (!formatOptions[trimmedKey]) formatOptions[trimmedKey] = val;
|
|
8309
|
+
if (val === 'false') formatOptions[trimmedKey] = false;
|
|
8310
|
+
if (val === 'true') formatOptions[trimmedKey] = true;
|
|
8311
|
+
if (!isNaN(val)) formatOptions[trimmedKey] = parseInt(val, 10);
|
|
8312
|
+
}
|
|
8313
|
+
});
|
|
8314
|
+
}
|
|
8315
|
+
}
|
|
8316
|
+
return {
|
|
8317
|
+
formatName,
|
|
8318
|
+
formatOptions
|
|
8319
|
+
};
|
|
8320
|
+
};
|
|
8321
|
+
const createCachedFormatter = fn => {
|
|
8322
|
+
const cache = {};
|
|
8323
|
+
return (v, l, o) => {
|
|
8324
|
+
let optForCache = o;
|
|
8325
|
+
if (o && o.interpolationkey && o.formatParams && o.formatParams[o.interpolationkey] && o[o.interpolationkey]) {
|
|
8326
|
+
optForCache = {
|
|
8327
|
+
...optForCache,
|
|
8328
|
+
[o.interpolationkey]: undefined
|
|
8329
|
+
};
|
|
8330
|
+
}
|
|
8331
|
+
const key = l + JSON.stringify(optForCache);
|
|
8332
|
+
let frm = cache[key];
|
|
8333
|
+
if (!frm) {
|
|
8334
|
+
frm = fn(getCleanedCode(l), o);
|
|
8335
|
+
cache[key] = frm;
|
|
8336
|
+
}
|
|
8337
|
+
return frm(v);
|
|
8338
|
+
};
|
|
8339
|
+
};
|
|
8340
|
+
const createNonCachedFormatter = fn => (v, l, o) => fn(getCleanedCode(l), o)(v);
|
|
8341
|
+
class Formatter {
|
|
8342
|
+
constructor(options = {}) {
|
|
8343
|
+
this.logger = baseLogger.create('formatter');
|
|
8344
|
+
this.options = options;
|
|
8345
|
+
this.init(options);
|
|
8346
|
+
}
|
|
8347
|
+
init(services, options = {
|
|
8348
|
+
interpolation: {}
|
|
8349
|
+
}) {
|
|
8350
|
+
this.formatSeparator = options.interpolation.formatSeparator || ',';
|
|
8351
|
+
const cf = options.cacheInBuiltFormats ? createCachedFormatter : createNonCachedFormatter;
|
|
8352
|
+
this.formats = {
|
|
8353
|
+
number: cf((lng, opt) => {
|
|
8354
|
+
const formatter = new Intl.NumberFormat(lng, {
|
|
8355
|
+
...opt
|
|
8356
|
+
});
|
|
8357
|
+
return val => formatter.format(val);
|
|
8358
|
+
}),
|
|
8359
|
+
currency: cf((lng, opt) => {
|
|
8360
|
+
const formatter = new Intl.NumberFormat(lng, {
|
|
8361
|
+
...opt,
|
|
8362
|
+
style: 'currency'
|
|
8363
|
+
});
|
|
8364
|
+
return val => formatter.format(val);
|
|
8365
|
+
}),
|
|
8366
|
+
datetime: cf((lng, opt) => {
|
|
8367
|
+
const formatter = new Intl.DateTimeFormat(lng, {
|
|
8368
|
+
...opt
|
|
8369
|
+
});
|
|
8370
|
+
return val => formatter.format(val);
|
|
8371
|
+
}),
|
|
8372
|
+
relativetime: cf((lng, opt) => {
|
|
8373
|
+
const formatter = new Intl.RelativeTimeFormat(lng, {
|
|
8374
|
+
...opt
|
|
8375
|
+
});
|
|
8376
|
+
return val => formatter.format(val, opt.range || 'day');
|
|
8377
|
+
}),
|
|
8378
|
+
list: cf((lng, opt) => {
|
|
8379
|
+
const formatter = new Intl.ListFormat(lng, {
|
|
8380
|
+
...opt
|
|
8381
|
+
});
|
|
8382
|
+
return val => formatter.format(val);
|
|
8383
|
+
})
|
|
8384
|
+
};
|
|
8385
|
+
}
|
|
8386
|
+
add(name, fc) {
|
|
8387
|
+
this.formats[name.toLowerCase().trim()] = fc;
|
|
8388
|
+
}
|
|
8389
|
+
addCached(name, fc) {
|
|
8390
|
+
this.formats[name.toLowerCase().trim()] = createCachedFormatter(fc);
|
|
8391
|
+
}
|
|
8392
|
+
format(value, format, lng, options = {}) {
|
|
8393
|
+
const formats = format.split(this.formatSeparator);
|
|
8394
|
+
if (formats.length > 1 && formats[0].indexOf('(') > 1 && formats[0].indexOf(')') < 0 && formats.find(f => f.indexOf(')') > -1)) {
|
|
8395
|
+
const lastIndex = formats.findIndex(f => f.indexOf(')') > -1);
|
|
8396
|
+
formats[0] = [formats[0], ...formats.splice(1, lastIndex)].join(this.formatSeparator);
|
|
8397
|
+
}
|
|
8398
|
+
const result = formats.reduce((mem, f) => {
|
|
8399
|
+
const {
|
|
8400
|
+
formatName,
|
|
8401
|
+
formatOptions
|
|
8402
|
+
} = parseFormatStr(f);
|
|
8403
|
+
if (this.formats[formatName]) {
|
|
8404
|
+
let formatted = mem;
|
|
8405
|
+
try {
|
|
8406
|
+
const valOptions = options?.formatParams?.[options.interpolationkey] || {};
|
|
8407
|
+
const l = valOptions.locale || valOptions.lng || options.locale || options.lng || lng;
|
|
8408
|
+
formatted = this.formats[formatName](mem, l, {
|
|
8409
|
+
...formatOptions,
|
|
8410
|
+
...options,
|
|
8411
|
+
...valOptions
|
|
8412
|
+
});
|
|
8413
|
+
} catch (error) {
|
|
8414
|
+
this.logger.warn(error);
|
|
8415
|
+
}
|
|
8416
|
+
return formatted;
|
|
8417
|
+
} else {
|
|
8418
|
+
this.logger.warn(`there was no format function for ${formatName}`);
|
|
8419
|
+
}
|
|
8420
|
+
return mem;
|
|
8421
|
+
}, value);
|
|
8422
|
+
return result;
|
|
8423
|
+
}
|
|
8424
|
+
}
|
|
8425
|
+
|
|
8426
|
+
const removePending = (q, name) => {
|
|
8427
|
+
if (q.pending[name] !== undefined) {
|
|
8428
|
+
delete q.pending[name];
|
|
8429
|
+
q.pendingCount--;
|
|
8430
|
+
}
|
|
8431
|
+
};
|
|
8432
|
+
class Connector extends EventEmitter {
|
|
8433
|
+
constructor(backend, store, services, options = {}) {
|
|
8434
|
+
super();
|
|
8435
|
+
this.backend = backend;
|
|
8436
|
+
this.store = store;
|
|
8437
|
+
this.services = services;
|
|
8438
|
+
this.languageUtils = services.languageUtils;
|
|
8439
|
+
this.options = options;
|
|
8440
|
+
this.logger = baseLogger.create('backendConnector');
|
|
8441
|
+
this.waitingReads = [];
|
|
8442
|
+
this.maxParallelReads = options.maxParallelReads || 10;
|
|
8443
|
+
this.readingCalls = 0;
|
|
8444
|
+
this.maxRetries = options.maxRetries >= 0 ? options.maxRetries : 5;
|
|
8445
|
+
this.retryTimeout = options.retryTimeout >= 1 ? options.retryTimeout : 350;
|
|
8446
|
+
this.state = {};
|
|
8447
|
+
this.queue = [];
|
|
8448
|
+
this.backend?.init?.(services, options.backend, options);
|
|
8449
|
+
}
|
|
8450
|
+
queueLoad(languages, namespaces, options, callback) {
|
|
8451
|
+
const toLoad = {};
|
|
8452
|
+
const pending = {};
|
|
8453
|
+
const toLoadLanguages = {};
|
|
8454
|
+
const toLoadNamespaces = {};
|
|
8455
|
+
languages.forEach(lng => {
|
|
8456
|
+
let hasAllNamespaces = true;
|
|
8457
|
+
namespaces.forEach(ns => {
|
|
8458
|
+
const name = `${lng}|${ns}`;
|
|
8459
|
+
if (!options.reload && this.store.hasResourceBundle(lng, ns)) {
|
|
8460
|
+
this.state[name] = 2;
|
|
8461
|
+
} else if (this.state[name] < 0) ; else if (this.state[name] === 1) {
|
|
8462
|
+
if (pending[name] === undefined) pending[name] = true;
|
|
8463
|
+
} else {
|
|
8464
|
+
this.state[name] = 1;
|
|
8465
|
+
hasAllNamespaces = false;
|
|
8466
|
+
if (pending[name] === undefined) pending[name] = true;
|
|
8467
|
+
if (toLoad[name] === undefined) toLoad[name] = true;
|
|
8468
|
+
if (toLoadNamespaces[ns] === undefined) toLoadNamespaces[ns] = true;
|
|
8469
|
+
}
|
|
8470
|
+
});
|
|
8471
|
+
if (!hasAllNamespaces) toLoadLanguages[lng] = true;
|
|
8472
|
+
});
|
|
8473
|
+
if (Object.keys(toLoad).length || Object.keys(pending).length) {
|
|
8474
|
+
this.queue.push({
|
|
8475
|
+
pending,
|
|
8476
|
+
pendingCount: Object.keys(pending).length,
|
|
8477
|
+
loaded: {},
|
|
8478
|
+
errors: [],
|
|
8479
|
+
callback
|
|
8480
|
+
});
|
|
8481
|
+
}
|
|
8482
|
+
return {
|
|
8483
|
+
toLoad: Object.keys(toLoad),
|
|
8484
|
+
pending: Object.keys(pending),
|
|
8485
|
+
toLoadLanguages: Object.keys(toLoadLanguages),
|
|
8486
|
+
toLoadNamespaces: Object.keys(toLoadNamespaces)
|
|
8487
|
+
};
|
|
8488
|
+
}
|
|
8489
|
+
loaded(name, err, data) {
|
|
8490
|
+
const s = name.split('|');
|
|
8491
|
+
const lng = s[0];
|
|
8492
|
+
const ns = s[1];
|
|
8493
|
+
if (err) this.emit('failedLoading', lng, ns, err);
|
|
8494
|
+
if (!err && data) {
|
|
8495
|
+
this.store.addResourceBundle(lng, ns, data, undefined, undefined, {
|
|
8496
|
+
skipCopy: true
|
|
8497
|
+
});
|
|
8498
|
+
}
|
|
8499
|
+
this.state[name] = err ? -1 : 2;
|
|
8500
|
+
if (err && data) this.state[name] = 0;
|
|
8501
|
+
const loaded = {};
|
|
8502
|
+
this.queue.forEach(q => {
|
|
8503
|
+
pushPath(q.loaded, [lng], ns);
|
|
8504
|
+
removePending(q, name);
|
|
8505
|
+
if (err) q.errors.push(err);
|
|
8506
|
+
if (q.pendingCount === 0 && !q.done) {
|
|
8507
|
+
Object.keys(q.loaded).forEach(l => {
|
|
8508
|
+
if (!loaded[l]) loaded[l] = {};
|
|
8509
|
+
const loadedKeys = q.loaded[l];
|
|
8510
|
+
if (loadedKeys.length) {
|
|
8511
|
+
loadedKeys.forEach(n => {
|
|
8512
|
+
if (loaded[l][n] === undefined) loaded[l][n] = true;
|
|
8513
|
+
});
|
|
8514
|
+
}
|
|
8515
|
+
});
|
|
8516
|
+
q.done = true;
|
|
8517
|
+
if (q.errors.length) {
|
|
8518
|
+
q.callback(q.errors);
|
|
8519
|
+
} else {
|
|
8520
|
+
q.callback();
|
|
8521
|
+
}
|
|
8522
|
+
}
|
|
8523
|
+
});
|
|
8524
|
+
this.emit('loaded', loaded);
|
|
8525
|
+
this.queue = this.queue.filter(q => !q.done);
|
|
8526
|
+
}
|
|
8527
|
+
read(lng, ns, fcName, tried = 0, wait = this.retryTimeout, callback) {
|
|
8528
|
+
if (!lng.length) return callback(null, {});
|
|
8529
|
+
if (this.readingCalls >= this.maxParallelReads) {
|
|
8530
|
+
this.waitingReads.push({
|
|
8531
|
+
lng,
|
|
8532
|
+
ns,
|
|
8533
|
+
fcName,
|
|
8534
|
+
tried,
|
|
8535
|
+
wait,
|
|
8536
|
+
callback
|
|
8537
|
+
});
|
|
8538
|
+
return;
|
|
8539
|
+
}
|
|
8540
|
+
this.readingCalls++;
|
|
8541
|
+
const resolver = (err, data) => {
|
|
8542
|
+
this.readingCalls--;
|
|
8543
|
+
if (this.waitingReads.length > 0) {
|
|
8544
|
+
const next = this.waitingReads.shift();
|
|
8545
|
+
this.read(next.lng, next.ns, next.fcName, next.tried, next.wait, next.callback);
|
|
8546
|
+
}
|
|
8547
|
+
if (err && data && tried < this.maxRetries) {
|
|
8548
|
+
setTimeout(() => {
|
|
8549
|
+
this.read.call(this, lng, ns, fcName, tried + 1, wait * 2, callback);
|
|
8550
|
+
}, wait);
|
|
8551
|
+
return;
|
|
8552
|
+
}
|
|
8553
|
+
callback(err, data);
|
|
8554
|
+
};
|
|
8555
|
+
const fc = this.backend[fcName].bind(this.backend);
|
|
8556
|
+
if (fc.length === 2) {
|
|
8557
|
+
try {
|
|
8558
|
+
const r = fc(lng, ns);
|
|
8559
|
+
if (r && typeof r.then === 'function') {
|
|
8560
|
+
r.then(data => resolver(null, data)).catch(resolver);
|
|
8561
|
+
} else {
|
|
8562
|
+
resolver(null, r);
|
|
8563
|
+
}
|
|
8564
|
+
} catch (err) {
|
|
8565
|
+
resolver(err);
|
|
8566
|
+
}
|
|
8567
|
+
return;
|
|
8568
|
+
}
|
|
8569
|
+
return fc(lng, ns, resolver);
|
|
8570
|
+
}
|
|
8571
|
+
prepareLoading(languages, namespaces, options = {}, callback) {
|
|
8572
|
+
if (!this.backend) {
|
|
8573
|
+
this.logger.warn('No backend was added via i18next.use. Will not load resources.');
|
|
8574
|
+
return callback && callback();
|
|
8575
|
+
}
|
|
8576
|
+
if (isString(languages)) languages = this.languageUtils.toResolveHierarchy(languages);
|
|
8577
|
+
if (isString(namespaces)) namespaces = [namespaces];
|
|
8578
|
+
const toLoad = this.queueLoad(languages, namespaces, options, callback);
|
|
8579
|
+
if (!toLoad.toLoad.length) {
|
|
8580
|
+
if (!toLoad.pending.length) callback();
|
|
8581
|
+
return null;
|
|
8582
|
+
}
|
|
8583
|
+
toLoad.toLoad.forEach(name => {
|
|
8584
|
+
this.loadOne(name);
|
|
8585
|
+
});
|
|
8586
|
+
}
|
|
8587
|
+
load(languages, namespaces, callback) {
|
|
8588
|
+
this.prepareLoading(languages, namespaces, {}, callback);
|
|
8589
|
+
}
|
|
8590
|
+
reload(languages, namespaces, callback) {
|
|
8591
|
+
this.prepareLoading(languages, namespaces, {
|
|
8592
|
+
reload: true
|
|
8593
|
+
}, callback);
|
|
8594
|
+
}
|
|
8595
|
+
loadOne(name, prefix = '') {
|
|
8596
|
+
const s = name.split('|');
|
|
8597
|
+
const lng = s[0];
|
|
8598
|
+
const ns = s[1];
|
|
8599
|
+
this.read(lng, ns, 'read', undefined, undefined, (err, data) => {
|
|
8600
|
+
if (err) this.logger.warn(`${prefix}loading namespace ${ns} for language ${lng} failed`, err);
|
|
8601
|
+
if (!err && data) this.logger.log(`${prefix}loaded namespace ${ns} for language ${lng}`, data);
|
|
8602
|
+
this.loaded(name, err, data);
|
|
8603
|
+
});
|
|
8604
|
+
}
|
|
8605
|
+
saveMissing(languages, namespace, key, fallbackValue, isUpdate, options = {}, clb = () => {}) {
|
|
8606
|
+
if (this.services?.utils?.hasLoadedNamespace && !this.services?.utils?.hasLoadedNamespace(namespace)) {
|
|
8607
|
+
this.logger.warn(`did not save key "${key}" as the namespace "${namespace}" was not yet loaded`, 'This means something IS WRONG in your setup. You access the t function before i18next.init / i18next.loadNamespace / i18next.changeLanguage was done. Wait for the callback or Promise to resolve before accessing it!!!');
|
|
8608
|
+
return;
|
|
8609
|
+
}
|
|
8610
|
+
if (key === undefined || key === null || key === '') return;
|
|
8611
|
+
if (this.backend?.create) {
|
|
8612
|
+
const opts = {
|
|
8613
|
+
...options,
|
|
8614
|
+
isUpdate
|
|
8615
|
+
};
|
|
8616
|
+
const fc = this.backend.create.bind(this.backend);
|
|
8617
|
+
if (fc.length < 6) {
|
|
8618
|
+
try {
|
|
8619
|
+
let r;
|
|
8620
|
+
if (fc.length === 5) {
|
|
8621
|
+
r = fc(languages, namespace, key, fallbackValue, opts);
|
|
8622
|
+
} else {
|
|
8623
|
+
r = fc(languages, namespace, key, fallbackValue);
|
|
8624
|
+
}
|
|
8625
|
+
if (r && typeof r.then === 'function') {
|
|
8626
|
+
r.then(data => clb(null, data)).catch(clb);
|
|
8627
|
+
} else {
|
|
8628
|
+
clb(null, r);
|
|
8629
|
+
}
|
|
8630
|
+
} catch (err) {
|
|
8631
|
+
clb(err);
|
|
8632
|
+
}
|
|
8633
|
+
} else {
|
|
8634
|
+
fc(languages, namespace, key, fallbackValue, clb, opts);
|
|
8635
|
+
}
|
|
8636
|
+
}
|
|
8637
|
+
if (!languages || !languages[0]) return;
|
|
8638
|
+
this.store.addResource(languages[0], namespace, key, fallbackValue);
|
|
8639
|
+
}
|
|
8640
|
+
}
|
|
8641
|
+
|
|
8642
|
+
const get = () => ({
|
|
8643
|
+
debug: false,
|
|
8644
|
+
initAsync: true,
|
|
8645
|
+
ns: ['translation'],
|
|
8646
|
+
defaultNS: ['translation'],
|
|
8647
|
+
fallbackLng: ['dev'],
|
|
8648
|
+
fallbackNS: false,
|
|
8649
|
+
supportedLngs: false,
|
|
8650
|
+
nonExplicitSupportedLngs: false,
|
|
8651
|
+
load: 'all',
|
|
8652
|
+
preload: false,
|
|
8653
|
+
simplifyPluralSuffix: true,
|
|
8654
|
+
keySeparator: '.',
|
|
8655
|
+
nsSeparator: ':',
|
|
8656
|
+
pluralSeparator: '_',
|
|
8657
|
+
contextSeparator: '_',
|
|
8658
|
+
partialBundledLanguages: false,
|
|
8659
|
+
saveMissing: false,
|
|
8660
|
+
updateMissing: false,
|
|
8661
|
+
saveMissingTo: 'fallback',
|
|
8662
|
+
saveMissingPlurals: true,
|
|
8663
|
+
missingKeyHandler: false,
|
|
8664
|
+
missingInterpolationHandler: false,
|
|
8665
|
+
postProcess: false,
|
|
8666
|
+
postProcessPassResolved: false,
|
|
8667
|
+
returnNull: false,
|
|
8668
|
+
returnEmptyString: true,
|
|
8669
|
+
returnObjects: false,
|
|
8670
|
+
joinArrays: false,
|
|
8671
|
+
returnedObjectHandler: false,
|
|
8672
|
+
parseMissingKeyHandler: false,
|
|
8673
|
+
appendNamespaceToMissingKey: false,
|
|
8674
|
+
appendNamespaceToCIMode: false,
|
|
8675
|
+
overloadTranslationOptionHandler: args => {
|
|
8676
|
+
let ret = {};
|
|
8677
|
+
if (typeof args[1] === 'object') ret = args[1];
|
|
8678
|
+
if (isString(args[1])) ret.defaultValue = args[1];
|
|
8679
|
+
if (isString(args[2])) ret.tDescription = args[2];
|
|
8680
|
+
if (typeof args[2] === 'object' || typeof args[3] === 'object') {
|
|
8681
|
+
const options = args[3] || args[2];
|
|
8682
|
+
Object.keys(options).forEach(key => {
|
|
8683
|
+
ret[key] = options[key];
|
|
8684
|
+
});
|
|
8685
|
+
}
|
|
8686
|
+
return ret;
|
|
8687
|
+
},
|
|
8688
|
+
interpolation: {
|
|
8689
|
+
escapeValue: true,
|
|
8690
|
+
format: value => value,
|
|
8691
|
+
prefix: '{{',
|
|
8692
|
+
suffix: '}}',
|
|
8693
|
+
formatSeparator: ',',
|
|
8694
|
+
unescapePrefix: '-',
|
|
8695
|
+
nestingPrefix: '$t(',
|
|
8696
|
+
nestingSuffix: ')',
|
|
8697
|
+
nestingOptionsSeparator: ',',
|
|
8698
|
+
maxReplaces: 1000,
|
|
8699
|
+
skipOnVariables: true
|
|
8700
|
+
},
|
|
8701
|
+
cacheInBuiltFormats: true
|
|
8702
|
+
});
|
|
8703
|
+
const transformOptions = options => {
|
|
8704
|
+
if (isString(options.ns)) options.ns = [options.ns];
|
|
8705
|
+
if (isString(options.fallbackLng)) options.fallbackLng = [options.fallbackLng];
|
|
8706
|
+
if (isString(options.fallbackNS)) options.fallbackNS = [options.fallbackNS];
|
|
8707
|
+
if (options.supportedLngs?.indexOf?.('cimode') < 0) {
|
|
8708
|
+
options.supportedLngs = options.supportedLngs.concat(['cimode']);
|
|
8709
|
+
}
|
|
8710
|
+
if (typeof options.initImmediate === 'boolean') options.initAsync = options.initImmediate;
|
|
8711
|
+
return options;
|
|
8712
|
+
};
|
|
8713
|
+
|
|
8714
|
+
const noop = () => {};
|
|
8715
|
+
const bindMemberFunctions = inst => {
|
|
8716
|
+
const mems = Object.getOwnPropertyNames(Object.getPrototypeOf(inst));
|
|
8717
|
+
mems.forEach(mem => {
|
|
8718
|
+
if (typeof inst[mem] === 'function') {
|
|
8719
|
+
inst[mem] = inst[mem].bind(inst);
|
|
8720
|
+
}
|
|
8721
|
+
});
|
|
8722
|
+
};
|
|
8723
|
+
class I18n extends EventEmitter {
|
|
8724
|
+
constructor(options = {}, callback) {
|
|
8725
|
+
super();
|
|
8726
|
+
this.options = transformOptions(options);
|
|
8727
|
+
this.services = {};
|
|
8728
|
+
this.logger = baseLogger;
|
|
8729
|
+
this.modules = {
|
|
8730
|
+
external: []
|
|
8731
|
+
};
|
|
8732
|
+
bindMemberFunctions(this);
|
|
8733
|
+
if (callback && !this.isInitialized && !options.isClone) {
|
|
8734
|
+
if (!this.options.initAsync) {
|
|
8735
|
+
this.init(options, callback);
|
|
8736
|
+
return this;
|
|
8737
|
+
}
|
|
8738
|
+
setTimeout(() => {
|
|
8739
|
+
this.init(options, callback);
|
|
8740
|
+
}, 0);
|
|
8741
|
+
}
|
|
8742
|
+
}
|
|
8743
|
+
init(options = {}, callback) {
|
|
8744
|
+
this.isInitializing = true;
|
|
8745
|
+
if (typeof options === 'function') {
|
|
8746
|
+
callback = options;
|
|
8747
|
+
options = {};
|
|
8748
|
+
}
|
|
8749
|
+
if (options.defaultNS == null && options.ns) {
|
|
8750
|
+
if (isString(options.ns)) {
|
|
8751
|
+
options.defaultNS = options.ns;
|
|
8752
|
+
} else if (options.ns.indexOf('translation') < 0) {
|
|
8753
|
+
options.defaultNS = options.ns[0];
|
|
8754
|
+
}
|
|
8755
|
+
}
|
|
8756
|
+
const defOpts = get();
|
|
8757
|
+
this.options = {
|
|
8758
|
+
...defOpts,
|
|
8759
|
+
...this.options,
|
|
8760
|
+
...transformOptions(options)
|
|
8761
|
+
};
|
|
8762
|
+
this.options.interpolation = {
|
|
8763
|
+
...defOpts.interpolation,
|
|
8764
|
+
...this.options.interpolation
|
|
8765
|
+
};
|
|
8766
|
+
if (options.keySeparator !== undefined) {
|
|
8767
|
+
this.options.userDefinedKeySeparator = options.keySeparator;
|
|
8768
|
+
}
|
|
8769
|
+
if (options.nsSeparator !== undefined) {
|
|
8770
|
+
this.options.userDefinedNsSeparator = options.nsSeparator;
|
|
8771
|
+
}
|
|
8772
|
+
if (typeof this.options.overloadTranslationOptionHandler !== 'function') {
|
|
8773
|
+
this.options.overloadTranslationOptionHandler = defOpts.overloadTranslationOptionHandler;
|
|
8774
|
+
}
|
|
8775
|
+
if (this.options.debug === true) {
|
|
8776
|
+
if (typeof console !== 'undefined') console.warn('i18next is maintained with support from locize.com — consider powering your project with managed localization (AI, CDN, integrations): https://locize.com');
|
|
8777
|
+
}
|
|
8778
|
+
const createClassOnDemand = ClassOrObject => {
|
|
8779
|
+
if (!ClassOrObject) return null;
|
|
8780
|
+
if (typeof ClassOrObject === 'function') return new ClassOrObject();
|
|
8781
|
+
return ClassOrObject;
|
|
8782
|
+
};
|
|
8783
|
+
if (!this.options.isClone) {
|
|
8784
|
+
if (this.modules.logger) {
|
|
8785
|
+
baseLogger.init(createClassOnDemand(this.modules.logger), this.options);
|
|
8786
|
+
} else {
|
|
8787
|
+
baseLogger.init(null, this.options);
|
|
8788
|
+
}
|
|
8789
|
+
let formatter;
|
|
8790
|
+
if (this.modules.formatter) {
|
|
8791
|
+
formatter = this.modules.formatter;
|
|
8792
|
+
} else {
|
|
8793
|
+
formatter = Formatter;
|
|
8794
|
+
}
|
|
8795
|
+
const lu = new LanguageUtil(this.options);
|
|
8796
|
+
this.store = new ResourceStore(this.options.resources, this.options);
|
|
8797
|
+
const s = this.services;
|
|
8798
|
+
s.logger = baseLogger;
|
|
8799
|
+
s.resourceStore = this.store;
|
|
8800
|
+
s.languageUtils = lu;
|
|
8801
|
+
s.pluralResolver = new PluralResolver(lu, {
|
|
8802
|
+
prepend: this.options.pluralSeparator,
|
|
8803
|
+
simplifyPluralSuffix: this.options.simplifyPluralSuffix
|
|
8804
|
+
});
|
|
8805
|
+
const usingLegacyFormatFunction = this.options.interpolation.format && this.options.interpolation.format !== defOpts.interpolation.format;
|
|
8806
|
+
if (usingLegacyFormatFunction) {
|
|
8807
|
+
this.logger.deprecate(`init: you are still using the legacy format function, please use the new approach: https://www.i18next.com/translation-function/formatting`);
|
|
8808
|
+
}
|
|
8809
|
+
if (formatter && (!this.options.interpolation.format || this.options.interpolation.format === defOpts.interpolation.format)) {
|
|
8810
|
+
s.formatter = createClassOnDemand(formatter);
|
|
8811
|
+
if (s.formatter.init) s.formatter.init(s, this.options);
|
|
8812
|
+
this.options.interpolation.format = s.formatter.format.bind(s.formatter);
|
|
8813
|
+
}
|
|
8814
|
+
s.interpolator = new Interpolator(this.options);
|
|
8815
|
+
s.utils = {
|
|
8816
|
+
hasLoadedNamespace: this.hasLoadedNamespace.bind(this)
|
|
8817
|
+
};
|
|
8818
|
+
s.backendConnector = new Connector(createClassOnDemand(this.modules.backend), s.resourceStore, s, this.options);
|
|
8819
|
+
s.backendConnector.on('*', (event, ...args) => {
|
|
8820
|
+
this.emit(event, ...args);
|
|
8821
|
+
});
|
|
8822
|
+
if (this.modules.languageDetector) {
|
|
8823
|
+
s.languageDetector = createClassOnDemand(this.modules.languageDetector);
|
|
8824
|
+
if (s.languageDetector.init) s.languageDetector.init(s, this.options.detection, this.options);
|
|
8825
|
+
}
|
|
8826
|
+
if (this.modules.i18nFormat) {
|
|
8827
|
+
s.i18nFormat = createClassOnDemand(this.modules.i18nFormat);
|
|
8828
|
+
if (s.i18nFormat.init) s.i18nFormat.init(this);
|
|
8829
|
+
}
|
|
8830
|
+
this.translator = new Translator(this.services, this.options);
|
|
8831
|
+
this.translator.on('*', (event, ...args) => {
|
|
8832
|
+
this.emit(event, ...args);
|
|
8833
|
+
});
|
|
8834
|
+
this.modules.external.forEach(m => {
|
|
8835
|
+
if (m.init) m.init(this);
|
|
8836
|
+
});
|
|
8837
|
+
}
|
|
8838
|
+
this.format = this.options.interpolation.format;
|
|
8839
|
+
if (!callback) callback = noop;
|
|
8840
|
+
if (this.options.fallbackLng && !this.services.languageDetector && !this.options.lng) {
|
|
8841
|
+
const codes = this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);
|
|
8842
|
+
if (codes.length > 0 && codes[0] !== 'dev') this.options.lng = codes[0];
|
|
8843
|
+
}
|
|
8844
|
+
if (!this.services.languageDetector && !this.options.lng) {
|
|
8845
|
+
this.logger.warn('init: no languageDetector is used and no lng is defined');
|
|
8846
|
+
}
|
|
8847
|
+
const storeApi = ['getResource', 'hasResourceBundle', 'getResourceBundle', 'getDataByLanguage'];
|
|
8848
|
+
storeApi.forEach(fcName => {
|
|
8849
|
+
this[fcName] = (...args) => this.store[fcName](...args);
|
|
8850
|
+
});
|
|
8851
|
+
const storeApiChained = ['addResource', 'addResources', 'addResourceBundle', 'removeResourceBundle'];
|
|
8852
|
+
storeApiChained.forEach(fcName => {
|
|
8853
|
+
this[fcName] = (...args) => {
|
|
8854
|
+
this.store[fcName](...args);
|
|
8855
|
+
return this;
|
|
8856
|
+
};
|
|
8857
|
+
});
|
|
8858
|
+
const deferred = defer();
|
|
8859
|
+
const load = () => {
|
|
8860
|
+
const finish = (err, t) => {
|
|
8861
|
+
this.isInitializing = false;
|
|
8862
|
+
if (this.isInitialized && !this.initializedStoreOnce) this.logger.warn('init: i18next is already initialized. You should call init just once!');
|
|
8863
|
+
this.isInitialized = true;
|
|
8864
|
+
if (!this.options.isClone) this.logger.log('initialized', this.options);
|
|
8865
|
+
this.emit('initialized', this.options);
|
|
8866
|
+
deferred.resolve(t);
|
|
8867
|
+
callback(err, t);
|
|
8868
|
+
};
|
|
8869
|
+
if (this.languages && !this.isInitialized) return finish(null, this.t.bind(this));
|
|
8870
|
+
this.changeLanguage(this.options.lng, finish);
|
|
8871
|
+
};
|
|
8872
|
+
if (this.options.resources || !this.options.initAsync) {
|
|
8873
|
+
load();
|
|
8874
|
+
} else {
|
|
8875
|
+
setTimeout(load, 0);
|
|
8876
|
+
}
|
|
8877
|
+
return deferred;
|
|
8878
|
+
}
|
|
8879
|
+
loadResources(language, callback = noop) {
|
|
8880
|
+
let usedCallback = callback;
|
|
8881
|
+
const usedLng = isString(language) ? language : this.language;
|
|
8882
|
+
if (typeof language === 'function') usedCallback = language;
|
|
8883
|
+
if (!this.options.resources || this.options.partialBundledLanguages) {
|
|
8884
|
+
if (usedLng?.toLowerCase() === 'cimode' && (!this.options.preload || this.options.preload.length === 0)) return usedCallback();
|
|
8885
|
+
const toLoad = [];
|
|
8886
|
+
const append = lng => {
|
|
8887
|
+
if (!lng) return;
|
|
8888
|
+
if (lng === 'cimode') return;
|
|
8889
|
+
const lngs = this.services.languageUtils.toResolveHierarchy(lng);
|
|
8890
|
+
lngs.forEach(l => {
|
|
8891
|
+
if (l === 'cimode') return;
|
|
8892
|
+
if (toLoad.indexOf(l) < 0) toLoad.push(l);
|
|
8893
|
+
});
|
|
8894
|
+
};
|
|
8895
|
+
if (!usedLng) {
|
|
8896
|
+
const fallbacks = this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);
|
|
8897
|
+
fallbacks.forEach(l => append(l));
|
|
8898
|
+
} else {
|
|
8899
|
+
append(usedLng);
|
|
8900
|
+
}
|
|
8901
|
+
this.options.preload?.forEach?.(l => append(l));
|
|
8902
|
+
this.services.backendConnector.load(toLoad, this.options.ns, e => {
|
|
8903
|
+
if (!e && !this.resolvedLanguage && this.language) this.setResolvedLanguage(this.language);
|
|
8904
|
+
usedCallback(e);
|
|
8905
|
+
});
|
|
8906
|
+
} else {
|
|
8907
|
+
usedCallback(null);
|
|
8908
|
+
}
|
|
8909
|
+
}
|
|
8910
|
+
reloadResources(lngs, ns, callback) {
|
|
8911
|
+
const deferred = defer();
|
|
8912
|
+
if (typeof lngs === 'function') {
|
|
8913
|
+
callback = lngs;
|
|
8914
|
+
lngs = undefined;
|
|
8915
|
+
}
|
|
8916
|
+
if (typeof ns === 'function') {
|
|
8917
|
+
callback = ns;
|
|
8918
|
+
ns = undefined;
|
|
8919
|
+
}
|
|
8920
|
+
if (!lngs) lngs = this.languages;
|
|
8921
|
+
if (!ns) ns = this.options.ns;
|
|
8922
|
+
if (!callback) callback = noop;
|
|
8923
|
+
this.services.backendConnector.reload(lngs, ns, err => {
|
|
8924
|
+
deferred.resolve();
|
|
8925
|
+
callback(err);
|
|
8926
|
+
});
|
|
8927
|
+
return deferred;
|
|
8928
|
+
}
|
|
8929
|
+
use(module) {
|
|
8930
|
+
if (!module) throw new Error('You are passing an undefined module! Please check the object you are passing to i18next.use()');
|
|
8931
|
+
if (!module.type) throw new Error('You are passing a wrong module! Please check the object you are passing to i18next.use()');
|
|
8932
|
+
if (module.type === 'backend') {
|
|
8933
|
+
this.modules.backend = module;
|
|
8934
|
+
}
|
|
8935
|
+
if (module.type === 'logger' || module.log && module.warn && module.error) {
|
|
8936
|
+
this.modules.logger = module;
|
|
8937
|
+
}
|
|
8938
|
+
if (module.type === 'languageDetector') {
|
|
8939
|
+
this.modules.languageDetector = module;
|
|
8940
|
+
}
|
|
8941
|
+
if (module.type === 'i18nFormat') {
|
|
8942
|
+
this.modules.i18nFormat = module;
|
|
8943
|
+
}
|
|
8944
|
+
if (module.type === 'postProcessor') {
|
|
8945
|
+
postProcessor.addPostProcessor(module);
|
|
8946
|
+
}
|
|
8947
|
+
if (module.type === 'formatter') {
|
|
8948
|
+
this.modules.formatter = module;
|
|
8949
|
+
}
|
|
8950
|
+
if (module.type === '3rdParty') {
|
|
8951
|
+
this.modules.external.push(module);
|
|
8952
|
+
}
|
|
8953
|
+
return this;
|
|
8954
|
+
}
|
|
8955
|
+
setResolvedLanguage(l) {
|
|
8956
|
+
if (!l || !this.languages) return;
|
|
8957
|
+
if (['cimode', 'dev'].indexOf(l) > -1) return;
|
|
8958
|
+
for (let li = 0; li < this.languages.length; li++) {
|
|
8959
|
+
const lngInLngs = this.languages[li];
|
|
8960
|
+
if (['cimode', 'dev'].indexOf(lngInLngs) > -1) continue;
|
|
8961
|
+
if (this.store.hasLanguageSomeTranslations(lngInLngs)) {
|
|
8962
|
+
this.resolvedLanguage = lngInLngs;
|
|
8963
|
+
break;
|
|
8964
|
+
}
|
|
8965
|
+
}
|
|
8966
|
+
if (!this.resolvedLanguage && this.languages.indexOf(l) < 0 && this.store.hasLanguageSomeTranslations(l)) {
|
|
8967
|
+
this.resolvedLanguage = l;
|
|
8968
|
+
this.languages.unshift(l);
|
|
8969
|
+
}
|
|
8970
|
+
}
|
|
8971
|
+
changeLanguage(lng, callback) {
|
|
8972
|
+
this.isLanguageChangingTo = lng;
|
|
8973
|
+
const deferred = defer();
|
|
8974
|
+
this.emit('languageChanging', lng);
|
|
8975
|
+
const setLngProps = l => {
|
|
8976
|
+
this.language = l;
|
|
8977
|
+
this.languages = this.services.languageUtils.toResolveHierarchy(l);
|
|
8978
|
+
this.resolvedLanguage = undefined;
|
|
8979
|
+
this.setResolvedLanguage(l);
|
|
8980
|
+
};
|
|
8981
|
+
const done = (err, l) => {
|
|
8982
|
+
if (l) {
|
|
8983
|
+
if (this.isLanguageChangingTo === lng) {
|
|
8984
|
+
setLngProps(l);
|
|
8985
|
+
this.translator.changeLanguage(l);
|
|
8986
|
+
this.isLanguageChangingTo = undefined;
|
|
8987
|
+
this.emit('languageChanged', l);
|
|
8988
|
+
this.logger.log('languageChanged', l);
|
|
8989
|
+
}
|
|
8990
|
+
} else {
|
|
8991
|
+
this.isLanguageChangingTo = undefined;
|
|
8992
|
+
}
|
|
8993
|
+
deferred.resolve((...args) => this.t(...args));
|
|
8994
|
+
if (callback) callback(err, (...args) => this.t(...args));
|
|
8995
|
+
};
|
|
8996
|
+
const setLng = lngs => {
|
|
8997
|
+
if (!lng && !lngs && this.services.languageDetector) lngs = [];
|
|
8998
|
+
const fl = isString(lngs) ? lngs : lngs && lngs[0];
|
|
8999
|
+
const l = this.store.hasLanguageSomeTranslations(fl) ? fl : this.services.languageUtils.getBestMatchFromCodes(isString(lngs) ? [lngs] : lngs);
|
|
9000
|
+
if (l) {
|
|
9001
|
+
if (!this.language) {
|
|
9002
|
+
setLngProps(l);
|
|
9003
|
+
}
|
|
9004
|
+
if (!this.translator.language) this.translator.changeLanguage(l);
|
|
9005
|
+
this.services.languageDetector?.cacheUserLanguage?.(l);
|
|
9006
|
+
}
|
|
9007
|
+
this.loadResources(l, err => {
|
|
9008
|
+
done(err, l);
|
|
9009
|
+
});
|
|
9010
|
+
};
|
|
9011
|
+
if (!lng && this.services.languageDetector && !this.services.languageDetector.async) {
|
|
9012
|
+
setLng(this.services.languageDetector.detect());
|
|
9013
|
+
} else if (!lng && this.services.languageDetector && this.services.languageDetector.async) {
|
|
9014
|
+
if (this.services.languageDetector.detect.length === 0) {
|
|
9015
|
+
this.services.languageDetector.detect().then(setLng);
|
|
9016
|
+
} else {
|
|
9017
|
+
this.services.languageDetector.detect(setLng);
|
|
9018
|
+
}
|
|
9019
|
+
} else {
|
|
9020
|
+
setLng(lng);
|
|
9021
|
+
}
|
|
9022
|
+
return deferred;
|
|
9023
|
+
}
|
|
9024
|
+
getFixedT(lng, ns, keyPrefix) {
|
|
9025
|
+
const fixedT = (key, opts, ...rest) => {
|
|
9026
|
+
let o;
|
|
9027
|
+
if (typeof opts !== 'object') {
|
|
9028
|
+
o = this.options.overloadTranslationOptionHandler([key, opts].concat(rest));
|
|
9029
|
+
} else {
|
|
9030
|
+
o = {
|
|
9031
|
+
...opts
|
|
9032
|
+
};
|
|
9033
|
+
}
|
|
9034
|
+
o.lng = o.lng || fixedT.lng;
|
|
9035
|
+
o.lngs = o.lngs || fixedT.lngs;
|
|
9036
|
+
o.ns = o.ns || fixedT.ns;
|
|
9037
|
+
if (o.keyPrefix !== '') o.keyPrefix = o.keyPrefix || keyPrefix || fixedT.keyPrefix;
|
|
9038
|
+
const keySeparator = this.options.keySeparator || '.';
|
|
9039
|
+
let resultKey;
|
|
9040
|
+
if (o.keyPrefix && Array.isArray(key)) {
|
|
9041
|
+
resultKey = key.map(k => {
|
|
9042
|
+
if (typeof k === 'function') k = keysFromSelector(k, {
|
|
9043
|
+
...this.options,
|
|
9044
|
+
...opts
|
|
9045
|
+
});
|
|
9046
|
+
return `${o.keyPrefix}${keySeparator}${k}`;
|
|
9047
|
+
});
|
|
9048
|
+
} else {
|
|
9049
|
+
if (typeof key === 'function') key = keysFromSelector(key, {
|
|
9050
|
+
...this.options,
|
|
9051
|
+
...opts
|
|
9052
|
+
});
|
|
9053
|
+
resultKey = o.keyPrefix ? `${o.keyPrefix}${keySeparator}${key}` : key;
|
|
9054
|
+
}
|
|
9055
|
+
return this.t(resultKey, o);
|
|
9056
|
+
};
|
|
9057
|
+
if (isString(lng)) {
|
|
9058
|
+
fixedT.lng = lng;
|
|
9059
|
+
} else {
|
|
9060
|
+
fixedT.lngs = lng;
|
|
9061
|
+
}
|
|
9062
|
+
fixedT.ns = ns;
|
|
9063
|
+
fixedT.keyPrefix = keyPrefix;
|
|
9064
|
+
return fixedT;
|
|
9065
|
+
}
|
|
9066
|
+
t(...args) {
|
|
9067
|
+
return this.translator?.translate(...args);
|
|
9068
|
+
}
|
|
9069
|
+
exists(...args) {
|
|
9070
|
+
return this.translator?.exists(...args);
|
|
9071
|
+
}
|
|
9072
|
+
setDefaultNamespace(ns) {
|
|
9073
|
+
this.options.defaultNS = ns;
|
|
9074
|
+
}
|
|
9075
|
+
hasLoadedNamespace(ns, options = {}) {
|
|
9076
|
+
if (!this.isInitialized) {
|
|
9077
|
+
this.logger.warn('hasLoadedNamespace: i18next was not initialized', this.languages);
|
|
9078
|
+
return false;
|
|
9079
|
+
}
|
|
9080
|
+
if (!this.languages || !this.languages.length) {
|
|
9081
|
+
this.logger.warn('hasLoadedNamespace: i18n.languages were undefined or empty', this.languages);
|
|
9082
|
+
return false;
|
|
9083
|
+
}
|
|
9084
|
+
const lng = options.lng || this.resolvedLanguage || this.languages[0];
|
|
9085
|
+
const fallbackLng = this.options ? this.options.fallbackLng : false;
|
|
9086
|
+
const lastLng = this.languages[this.languages.length - 1];
|
|
9087
|
+
if (lng.toLowerCase() === 'cimode') return true;
|
|
9088
|
+
const loadNotPending = (l, n) => {
|
|
9089
|
+
const loadState = this.services.backendConnector.state[`${l}|${n}`];
|
|
9090
|
+
return loadState === -1 || loadState === 0 || loadState === 2;
|
|
9091
|
+
};
|
|
9092
|
+
if (options.precheck) {
|
|
9093
|
+
const preResult = options.precheck(this, loadNotPending);
|
|
9094
|
+
if (preResult !== undefined) return preResult;
|
|
9095
|
+
}
|
|
9096
|
+
if (this.hasResourceBundle(lng, ns)) return true;
|
|
9097
|
+
if (!this.services.backendConnector.backend || this.options.resources && !this.options.partialBundledLanguages) return true;
|
|
9098
|
+
if (loadNotPending(lng, ns) && (!fallbackLng || loadNotPending(lastLng, ns))) return true;
|
|
9099
|
+
return false;
|
|
9100
|
+
}
|
|
9101
|
+
loadNamespaces(ns, callback) {
|
|
9102
|
+
const deferred = defer();
|
|
9103
|
+
if (!this.options.ns) {
|
|
9104
|
+
if (callback) callback();
|
|
9105
|
+
return Promise.resolve();
|
|
9106
|
+
}
|
|
9107
|
+
if (isString(ns)) ns = [ns];
|
|
9108
|
+
ns.forEach(n => {
|
|
9109
|
+
if (this.options.ns.indexOf(n) < 0) this.options.ns.push(n);
|
|
9110
|
+
});
|
|
9111
|
+
this.loadResources(err => {
|
|
9112
|
+
deferred.resolve();
|
|
9113
|
+
if (callback) callback(err);
|
|
9114
|
+
});
|
|
9115
|
+
return deferred;
|
|
9116
|
+
}
|
|
9117
|
+
loadLanguages(lngs, callback) {
|
|
9118
|
+
const deferred = defer();
|
|
9119
|
+
if (isString(lngs)) lngs = [lngs];
|
|
9120
|
+
const preloaded = this.options.preload || [];
|
|
9121
|
+
const newLngs = lngs.filter(lng => preloaded.indexOf(lng) < 0 && this.services.languageUtils.isSupportedCode(lng));
|
|
9122
|
+
if (!newLngs.length) {
|
|
9123
|
+
if (callback) callback();
|
|
9124
|
+
return Promise.resolve();
|
|
9125
|
+
}
|
|
9126
|
+
this.options.preload = preloaded.concat(newLngs);
|
|
9127
|
+
this.loadResources(err => {
|
|
9128
|
+
deferred.resolve();
|
|
9129
|
+
if (callback) callback(err);
|
|
9130
|
+
});
|
|
9131
|
+
return deferred;
|
|
9132
|
+
}
|
|
9133
|
+
dir(lng) {
|
|
9134
|
+
if (!lng) lng = this.resolvedLanguage || (this.languages?.length > 0 ? this.languages[0] : this.language);
|
|
9135
|
+
if (!lng) return 'rtl';
|
|
9136
|
+
try {
|
|
9137
|
+
const l = new Intl.Locale(lng);
|
|
9138
|
+
if (l && l.getTextInfo) {
|
|
9139
|
+
const ti = l.getTextInfo();
|
|
9140
|
+
if (ti && ti.direction) return ti.direction;
|
|
9141
|
+
}
|
|
9142
|
+
} catch (e) {}
|
|
9143
|
+
const rtlLngs = ['ar', 'shu', 'sqr', 'ssh', 'xaa', 'yhd', 'yud', 'aao', 'abh', 'abv', 'acm', 'acq', 'acw', 'acx', 'acy', 'adf', 'ads', 'aeb', 'aec', 'afb', 'ajp', 'apc', 'apd', 'arb', 'arq', 'ars', 'ary', 'arz', 'auz', 'avl', 'ayh', 'ayl', 'ayn', 'ayp', 'bbz', 'pga', 'he', 'iw', 'ps', 'pbt', 'pbu', 'pst', 'prp', 'prd', 'ug', 'ur', 'ydd', 'yds', 'yih', 'ji', 'yi', 'hbo', 'men', 'xmn', 'fa', 'jpr', 'peo', 'pes', 'prs', 'dv', 'sam', 'ckb'];
|
|
9144
|
+
const languageUtils = this.services?.languageUtils || new LanguageUtil(get());
|
|
9145
|
+
if (lng.toLowerCase().indexOf('-latn') > 1) return 'ltr';
|
|
9146
|
+
return rtlLngs.indexOf(languageUtils.getLanguagePartFromCode(lng)) > -1 || lng.toLowerCase().indexOf('-arab') > 1 ? 'rtl' : 'ltr';
|
|
9147
|
+
}
|
|
9148
|
+
static createInstance(options = {}, callback) {
|
|
9149
|
+
const instance = new I18n(options, callback);
|
|
9150
|
+
instance.createInstance = I18n.createInstance;
|
|
9151
|
+
return instance;
|
|
9152
|
+
}
|
|
9153
|
+
cloneInstance(options = {}, callback = noop) {
|
|
9154
|
+
const forkResourceStore = options.forkResourceStore;
|
|
9155
|
+
if (forkResourceStore) delete options.forkResourceStore;
|
|
9156
|
+
const mergedOptions = {
|
|
9157
|
+
...this.options,
|
|
9158
|
+
...options,
|
|
9159
|
+
...{
|
|
9160
|
+
isClone: true
|
|
9161
|
+
}
|
|
9162
|
+
};
|
|
9163
|
+
const clone = new I18n(mergedOptions);
|
|
9164
|
+
if (options.debug !== undefined || options.prefix !== undefined) {
|
|
9165
|
+
clone.logger = clone.logger.clone(options);
|
|
9166
|
+
}
|
|
9167
|
+
const membersToCopy = ['store', 'services', 'language'];
|
|
9168
|
+
membersToCopy.forEach(m => {
|
|
9169
|
+
clone[m] = this[m];
|
|
9170
|
+
});
|
|
9171
|
+
clone.services = {
|
|
9172
|
+
...this.services
|
|
9173
|
+
};
|
|
9174
|
+
clone.services.utils = {
|
|
9175
|
+
hasLoadedNamespace: clone.hasLoadedNamespace.bind(clone)
|
|
9176
|
+
};
|
|
9177
|
+
if (forkResourceStore) {
|
|
9178
|
+
const clonedData = Object.keys(this.store.data).reduce((prev, l) => {
|
|
9179
|
+
prev[l] = {
|
|
9180
|
+
...this.store.data[l]
|
|
6888
9181
|
};
|
|
9182
|
+
prev[l] = Object.keys(prev[l]).reduce((acc, n) => {
|
|
9183
|
+
acc[n] = {
|
|
9184
|
+
...prev[l][n]
|
|
9185
|
+
};
|
|
9186
|
+
return acc;
|
|
9187
|
+
}, prev[l]);
|
|
9188
|
+
return prev;
|
|
9189
|
+
}, {});
|
|
9190
|
+
clone.store = new ResourceStore(clonedData, mergedOptions);
|
|
9191
|
+
clone.services.resourceStore = clone.store;
|
|
9192
|
+
}
|
|
9193
|
+
if (options.interpolation) {
|
|
9194
|
+
const defOpts = get();
|
|
9195
|
+
const mergedInterpolation = {
|
|
9196
|
+
...defOpts.interpolation,
|
|
9197
|
+
...this.options.interpolation,
|
|
9198
|
+
...options.interpolation
|
|
9199
|
+
};
|
|
9200
|
+
const mergedForInterpolator = {
|
|
9201
|
+
...mergedOptions,
|
|
9202
|
+
interpolation: mergedInterpolation
|
|
9203
|
+
};
|
|
9204
|
+
clone.services.interpolator = new Interpolator(mergedForInterpolator);
|
|
6889
9205
|
}
|
|
9206
|
+
clone.translator = new Translator(clone.services, mergedOptions);
|
|
9207
|
+
clone.translator.on('*', (event, ...args) => {
|
|
9208
|
+
clone.emit(event, ...args);
|
|
9209
|
+
});
|
|
9210
|
+
clone.init(mergedOptions, callback);
|
|
9211
|
+
clone.translator.options = mergedOptions;
|
|
9212
|
+
clone.translator.backendConnector.services.utils = {
|
|
9213
|
+
hasLoadedNamespace: clone.hasLoadedNamespace.bind(clone)
|
|
9214
|
+
};
|
|
9215
|
+
return clone;
|
|
9216
|
+
}
|
|
9217
|
+
toJSON() {
|
|
9218
|
+
return {
|
|
9219
|
+
options: this.options,
|
|
9220
|
+
store: this.store,
|
|
9221
|
+
language: this.language,
|
|
9222
|
+
languages: this.languages,
|
|
9223
|
+
resolvedLanguage: this.resolvedLanguage
|
|
9224
|
+
};
|
|
9225
|
+
}
|
|
6890
9226
|
}
|
|
9227
|
+
const instance = I18n.createInstance();
|
|
6891
9228
|
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
9229
|
+
instance.createInstance;
|
|
9230
|
+
instance.dir;
|
|
9231
|
+
instance.init;
|
|
9232
|
+
instance.loadResources;
|
|
9233
|
+
instance.reloadResources;
|
|
9234
|
+
instance.use;
|
|
9235
|
+
instance.changeLanguage;
|
|
9236
|
+
instance.getFixedT;
|
|
9237
|
+
instance.t;
|
|
9238
|
+
instance.exists;
|
|
9239
|
+
instance.setDefaultNamespace;
|
|
9240
|
+
instance.hasLoadedNamespace;
|
|
9241
|
+
instance.loadNamespaces;
|
|
9242
|
+
instance.loadLanguages;
|
|
6904
9243
|
|
|
6905
9244
|
/**
|
|
6906
|
-
*
|
|
6907
|
-
* Now that all components use Tailwind CSS, we only need basic theme switching
|
|
9245
|
+
* English translations for map-gl-offline
|
|
6908
9246
|
*/
|
|
6909
|
-
const
|
|
6910
|
-
|
|
6911
|
-
|
|
9247
|
+
const en = {
|
|
9248
|
+
// General
|
|
9249
|
+
'app.title': 'Offline Manager',
|
|
9250
|
+
'app.close': 'Close',
|
|
9251
|
+
'app.cancel': 'Cancel',
|
|
9252
|
+
'app.save': 'Save',
|
|
9253
|
+
'app.delete': 'Delete',
|
|
9254
|
+
'app.confirm': 'Confirm',
|
|
9255
|
+
'app.ok': 'OK',
|
|
9256
|
+
'app.loading': 'Loading...',
|
|
9257
|
+
'app.error': 'Error',
|
|
9258
|
+
'app.success': 'Success',
|
|
9259
|
+
'app.warning': 'Warning',
|
|
9260
|
+
'app.refresh': 'Refresh',
|
|
9261
|
+
'app.details': 'Details',
|
|
9262
|
+
'app.focus': 'Focus',
|
|
9263
|
+
// Theme
|
|
9264
|
+
'theme.toggle': 'Toggle theme',
|
|
9265
|
+
'theme.light': 'Light',
|
|
9266
|
+
'theme.dark': 'Dark',
|
|
9267
|
+
// Language
|
|
9268
|
+
'language.select': 'Select language',
|
|
9269
|
+
'language.en': 'English',
|
|
9270
|
+
'language.ar': 'Arabic',
|
|
9271
|
+
// Header
|
|
9272
|
+
'header.title': 'Offline Manager',
|
|
9273
|
+
'header.subtitle': '{{count}} regions • {{size}} total',
|
|
9274
|
+
// Actions
|
|
9275
|
+
'actions.addRegion': 'Add Region',
|
|
9276
|
+
'actions.refresh': 'Refresh',
|
|
9277
|
+
'actions.loadStyle': 'Load Style',
|
|
9278
|
+
'actions.fixTiles': 'Fix Tiles',
|
|
9279
|
+
'actions.deleteStyle': 'Delete Style',
|
|
9280
|
+
'actions.redownload': 'Re-download',
|
|
9281
|
+
'actions.importExport': 'Import/Export',
|
|
9282
|
+
// Region Form
|
|
9283
|
+
'regionForm.title': 'Download Offline Region',
|
|
9284
|
+
'regionForm.subtitle': 'Selected area: {{area}} km²',
|
|
9285
|
+
'regionForm.name': 'Region Name',
|
|
9286
|
+
'regionForm.namePlaceholder': 'e.g., Downtown District',
|
|
9287
|
+
'regionForm.minZoom': 'Min Zoom',
|
|
9288
|
+
'regionForm.maxZoom': 'Max Zoom',
|
|
9289
|
+
'regionForm.styleUrl': 'Style URL',
|
|
9290
|
+
'regionForm.provider': 'Style Provider',
|
|
9291
|
+
'regionForm.providerAuto': 'Auto-detect',
|
|
9292
|
+
'regionForm.providerMapbox': 'Mapbox GL (requires access token)',
|
|
9293
|
+
'regionForm.providerMaplibre': 'MapLibre GL (open source)',
|
|
9294
|
+
'regionForm.providerInfo': 'Auto-detect will analyze the style URL to determine the provider',
|
|
9295
|
+
'regionForm.accessToken': 'Mapbox Access Token',
|
|
9296
|
+
'regionForm.accessTokenRequired': 'Required',
|
|
9297
|
+
'regionForm.accessTokenHelp': 'Get your access token from',
|
|
9298
|
+
'regionForm.downloadRegion': 'Download Region',
|
|
9299
|
+
'regionForm.area': 'Area',
|
|
9300
|
+
'regionForm.bounds': 'Bounds',
|
|
9301
|
+
// Region List
|
|
9302
|
+
'regionList.empty': 'No offline styles or regions found. Click "Add Region" to get started.',
|
|
9303
|
+
'regionList.emptyFallback': 'No offline regions found. Click "Add Region" to get started.',
|
|
9304
|
+
'regionList.orphanedHeader': 'Regions without Style',
|
|
9305
|
+
'regionList.orphanedDescription': '{{count}} region(s) not associated with any style',
|
|
9306
|
+
'regionList.zoom': 'Zoom',
|
|
9307
|
+
'regionList.downloaded': 'Downloaded',
|
|
9308
|
+
'regionList.size': 'Size',
|
|
9309
|
+
'regionList.styleId': 'Style ID',
|
|
9310
|
+
'regionList.storedStyleSize': 'Stored Style Size',
|
|
9311
|
+
// Region Details
|
|
9312
|
+
'regionDetails.title': 'Region Details',
|
|
9313
|
+
'regionDetails.focusOnMap': 'Focus on Map',
|
|
9314
|
+
// Style
|
|
9315
|
+
'style.unnamedStyle': 'Unnamed Style',
|
|
9316
|
+
'style.regions': '{{count}} region(s)',
|
|
9317
|
+
// Delete Confirmation
|
|
9318
|
+
'delete.regionTitle': 'Delete Region',
|
|
9319
|
+
'delete.regionMessage': 'Are you sure you want to delete the region "{{name}}"? This action cannot be undone.',
|
|
9320
|
+
'delete.styleTitle': 'Delete Style',
|
|
9321
|
+
'delete.styleMessage': 'Are you sure you want to delete the style "{{name}}"? This action cannot be undone and will affect associated regions.',
|
|
9322
|
+
// Re-download
|
|
9323
|
+
'redownload.title': 'Re-download Region',
|
|
9324
|
+
'redownload.message': 'Re-download "{{name}}"?\n\nThis will:\n• Delete existing tiles and resources\n• Re-download all data with current settings\n• Fix any corrupted or outdated resources',
|
|
9325
|
+
'redownload.button': 'Re-download',
|
|
9326
|
+
// Fix Tiles
|
|
9327
|
+
'fixTiles.noIssuesTitle': 'No Issues Found',
|
|
9328
|
+
'fixTiles.noIssuesMessage': 'No compressed tiles detected. Your tiles are already in the correct format!',
|
|
9329
|
+
'fixTiles.title': 'Fix Compressed Tiles',
|
|
9330
|
+
'fixTiles.message': "Found {{count}} compressed tiles that may cause rendering errors.\n\nThis will:\n1. Remove all compressed tiles ({{count}} tiles)\n2. You'll need to re-download regions to get properly decompressed tiles\n\nContinue?",
|
|
9331
|
+
'fixTiles.button': 'Fix Tiles',
|
|
9332
|
+
'fixTiles.completeTitle': 'Cleanup Complete',
|
|
9333
|
+
'fixTiles.completeMessage': 'Successfully removed {{count}} compressed tiles.\n\nPlease re-download your regions to get the fixed tiles.',
|
|
9334
|
+
// Download Progress
|
|
9335
|
+
'download.active': 'Active Downloads',
|
|
9336
|
+
'download.phase.style': 'Style',
|
|
9337
|
+
'download.phase.sprites': 'Sprites',
|
|
9338
|
+
'download.phase.glyphs': 'Glyphs',
|
|
9339
|
+
'download.phase.tiles': 'Tiles',
|
|
9340
|
+
// Style Selection
|
|
9341
|
+
'styleSelection.title': 'Select Offline Style',
|
|
9342
|
+
'styleSelection.message': 'Choose which offline style to load:',
|
|
9343
|
+
'styleSelection.sources': 'sources',
|
|
9344
|
+
// Import/Export
|
|
9345
|
+
'importExport.title': 'Import/Export',
|
|
9346
|
+
'importExport.export': 'Export',
|
|
9347
|
+
'importExport.import': 'Import',
|
|
9348
|
+
// Errors
|
|
9349
|
+
'error.loadingContent': 'Error loading content',
|
|
9350
|
+
'error.tryAgain': 'Please try again',
|
|
9351
|
+
'error.mapNotInitialized': 'Map is not initialized. Please ensure the map is loaded before loading a style.',
|
|
9352
|
+
'error.invalidStyleData': 'Invalid style data. The style may be corrupted.',
|
|
9353
|
+
'error.styleMissingSources': 'Style has no sources defined. The style may be incomplete.',
|
|
9354
|
+
'error.styleMissingLayers': 'Style has no layers defined. The style may be incomplete.',
|
|
9355
|
+
'error.regionNotFound': 'Region not found',
|
|
9356
|
+
'error.downloadFailed': 'Download failed',
|
|
9357
|
+
'error.deleteFailed': 'Failed to delete',
|
|
9358
|
+
'error.loadStyleFailed': 'Failed to load style',
|
|
9359
|
+
// Warnings
|
|
9360
|
+
'warning.compressedTiles': 'Found {{count}} compressed tiles that may cause rendering issues.',
|
|
9361
|
+
'warning.compressedTilesRecommendation': 'Recommended: Use the "Re-download" button to fix this issue.',
|
|
9362
|
+
'warning.continueAnyway': 'Do you want to continue loading the style anyway?',
|
|
9363
|
+
// Drawing Tool
|
|
9364
|
+
'drawing.instructions': 'Click and drag to draw a region',
|
|
9365
|
+
'drawing.cancel': 'Press ESC to cancel',
|
|
9366
|
+
// Offline Maps Button
|
|
9367
|
+
'button.offlineMaps': 'Offline Maps',
|
|
9368
|
+
// Region Details Modal
|
|
9369
|
+
'regionDetails.bounds': 'Bounds',
|
|
9370
|
+
'regionDetails.zoomRange': 'Zoom Range',
|
|
9371
|
+
'regionDetails.created': 'Created',
|
|
9372
|
+
// Import/Export Modal
|
|
9373
|
+
'importExport.regionTitle': 'Import/Export Region',
|
|
9374
|
+
'importExport.regionInfo': 'Region Information',
|
|
9375
|
+
'importExport.id': 'ID',
|
|
9376
|
+
'importExport.name': 'Name',
|
|
9377
|
+
'importExport.unnamed': 'Unnamed',
|
|
9378
|
+
'importExport.zoom': 'Zoom',
|
|
9379
|
+
'importExport.created': 'Created',
|
|
9380
|
+
'importExport.exportRegion': 'Export Region',
|
|
9381
|
+
'importExport.exportFormat': 'Export Format',
|
|
9382
|
+
'importExport.formatJson': 'JSON - Complete data (recommended)',
|
|
9383
|
+
'importExport.formatPmtiles': 'PMTiles - Web optimized tiles',
|
|
9384
|
+
'importExport.formatMbtiles': 'MBTiles - Industry standard',
|
|
9385
|
+
'importExport.formatHint': 'Choose format based on your use case',
|
|
9386
|
+
'importExport.includeComponents': 'Include Components',
|
|
9387
|
+
'importExport.styleConfig': 'Style Configuration',
|
|
9388
|
+
'importExport.mapTiles': 'Map Tiles',
|
|
9389
|
+
'importExport.spritesIcons': 'Sprites & Icons',
|
|
9390
|
+
'importExport.fontsGlyphs': 'Fonts & Glyphs',
|
|
9391
|
+
'importExport.preparingExport': 'Preparing export...',
|
|
9392
|
+
'importExport.exportComplete': 'Export complete!',
|
|
9393
|
+
'importExport.exportFailed': 'Export failed. Please try again.',
|
|
9394
|
+
'importExport.importRegion': 'Import Region',
|
|
9395
|
+
'importExport.selectFile': 'Select File',
|
|
9396
|
+
'importExport.fileFormatsHint': 'Supports JSON, PMTiles, and MBTiles formats',
|
|
9397
|
+
'importExport.newRegionName': 'New Region Name (Optional)',
|
|
9398
|
+
'importExport.newRegionNamePlaceholder': 'Leave empty to use original name',
|
|
9399
|
+
'importExport.overwriteIfExists': 'Overwrite if region exists',
|
|
9400
|
+
'importExport.preparingImport': 'Preparing import...',
|
|
9401
|
+
'importExport.importComplete': 'Import complete!',
|
|
9402
|
+
'importExport.importFailed': 'Import failed. Please try again.',
|
|
9403
|
+
'importExport.formatGuide': 'Format Guide',
|
|
9404
|
+
'importExport.jsonDesc': 'Complete data, human-readable, best for development',
|
|
9405
|
+
'importExport.pmtilesDesc': 'Web-optimized, efficient serving, cloud-friendly',
|
|
9406
|
+
'importExport.mbtilesDesc': 'Industry standard, SQLite-based, cross-platform',
|
|
9407
|
+
// Active Downloads
|
|
9408
|
+
'download.activeCount': 'Active Downloads ({{count}})',
|
|
9409
|
+
// Panel Manager additional strings
|
|
9410
|
+
'panel.downloadedRegions': 'Downloaded Regions',
|
|
9411
|
+
'panel.noExpiry': 'No expiry',
|
|
9412
|
+
'panel.noBounds': 'No bounds',
|
|
9413
|
+
'panel.sources': 'sources',
|
|
9414
|
+
// Empty/Error States
|
|
9415
|
+
'emptyState.title': 'No items',
|
|
9416
|
+
'emptyState.message': 'There are no items to display',
|
|
9417
|
+
'errorState.message': 'Something went wrong',
|
|
9418
|
+
'errorState.retry': 'Try Again',
|
|
9419
|
+
// List
|
|
9420
|
+
'list.empty': 'No items to display',
|
|
9421
|
+
'list.noItemsFound': 'No items found',
|
|
9422
|
+
// Toast
|
|
9423
|
+
'toast.dismiss': 'Dismiss notification',
|
|
9424
|
+
// Control Buttons
|
|
9425
|
+
'control.offlineMapManager': 'Offline Map Manager',
|
|
9426
|
+
'control.cancelSelection': 'Cancel Selection',
|
|
9427
|
+
'control.saveSelectedRegion': 'Save Selected Region',
|
|
9428
|
+
// Alerts/Messages
|
|
9429
|
+
'alert.unknownError': 'Unknown error occurred',
|
|
9430
|
+
'alert.downloadNotImplemented': 'Download feature not implemented in this refactored version',
|
|
9431
|
+
'alert.mapNotAvailable': 'Map not available',
|
|
9432
|
+
'alert.invalidBounds': 'Invalid bounds for region',
|
|
9433
|
+
'alert.styleNotFound': 'Style not found',
|
|
9434
|
+
'alert.noStylesFound': 'No styles found in IndexedDB',
|
|
9435
|
+
'alert.failedToApplyStyle': 'Failed to apply style to map',
|
|
9436
|
+
'alert.failedToLoadStyle': 'Failed to load style',
|
|
9437
|
+
'alert.failedToRedownload': 'Failed to re-download region',
|
|
9438
|
+
'alert.downloadManagerNotAvailable': 'Download manager not available',
|
|
9439
|
+
// Download Manager
|
|
9440
|
+
'downloadManager.downloadingStyle': 'Downloading style',
|
|
9441
|
+
'downloadManager.downloadingSprites': 'Downloading sprites',
|
|
9442
|
+
'downloadManager.downloadingGlyphs': 'Downloading glyphs',
|
|
9443
|
+
'downloadManager.downloadingTiles': 'Downloading tiles',
|
|
9444
|
+
'downloadManager.styleExistsNotFound': 'Style exists but could not be found',
|
|
9445
|
+
'downloadManager.styleNotFoundForResources': 'Style not found for resource download',
|
|
9446
|
+
'downloadManager.styleNotFoundForTiles': 'Style not found for tile download',
|
|
9447
|
+
'downloadManager.styleNoSources': 'Style does not contain any sources for tile download',
|
|
9448
|
+
// Form Validation
|
|
9449
|
+
'validation.required': 'This field is required',
|
|
9450
|
+
'validation.invalidFormat': 'Invalid format',
|
|
9451
|
+
'validation.mustBeNumber': 'Must be a number',
|
|
9452
|
+
'validation.mustBeWholeNumber': 'Must be a whole number',
|
|
9453
|
+
'validation.invalidUrl': 'Invalid URL format',
|
|
6912
9454
|
};
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
9455
|
+
|
|
9456
|
+
/**
|
|
9457
|
+
* Arabic translations for map-gl-offline
|
|
9458
|
+
* الترجمة العربية لـ map-gl-offline
|
|
9459
|
+
*/
|
|
9460
|
+
const ar = {
|
|
9461
|
+
// General - عام
|
|
9462
|
+
'app.title': 'مدير الخرائط غير المتصلة',
|
|
9463
|
+
'app.close': 'إغلاق',
|
|
9464
|
+
'app.cancel': 'إلغاء',
|
|
9465
|
+
'app.save': 'حفظ',
|
|
9466
|
+
'app.delete': 'حذف',
|
|
9467
|
+
'app.confirm': 'تأكيد',
|
|
9468
|
+
'app.ok': 'موافق',
|
|
9469
|
+
'app.loading': 'جاري التحميل...',
|
|
9470
|
+
'app.error': 'خطأ',
|
|
9471
|
+
'app.success': 'نجاح',
|
|
9472
|
+
'app.warning': 'تحذير',
|
|
9473
|
+
'app.refresh': 'تحديث',
|
|
9474
|
+
'app.details': 'التفاصيل',
|
|
9475
|
+
'app.focus': 'التركيز',
|
|
9476
|
+
// Theme - السمة
|
|
9477
|
+
'theme.toggle': 'تبديل السمة',
|
|
9478
|
+
'theme.light': 'فاتح',
|
|
9479
|
+
'theme.dark': 'داكن',
|
|
9480
|
+
// Language - اللغة
|
|
9481
|
+
'language.select': 'اختر اللغة',
|
|
9482
|
+
'language.en': 'الإنجليزية',
|
|
9483
|
+
'language.ar': 'العربية',
|
|
9484
|
+
// Header - الرأس
|
|
9485
|
+
'header.title': 'مدير الخرائط غير المتصلة',
|
|
9486
|
+
'header.subtitle': '{{count}} منطقة • {{size}} إجمالي',
|
|
9487
|
+
// Actions - الإجراءات
|
|
9488
|
+
'actions.addRegion': 'إضافة منطقة',
|
|
9489
|
+
'actions.refresh': 'تحديث',
|
|
9490
|
+
'actions.loadStyle': 'تحميل النمط',
|
|
9491
|
+
'actions.fixTiles': 'إصلاح البلاطات',
|
|
9492
|
+
'actions.deleteStyle': 'حذف النمط',
|
|
9493
|
+
'actions.redownload': 'إعادة التحميل',
|
|
9494
|
+
'actions.importExport': 'استيراد/تصدير',
|
|
9495
|
+
// Region Form - نموذج المنطقة
|
|
9496
|
+
'regionForm.title': 'تحميل منطقة غير متصلة',
|
|
9497
|
+
'regionForm.subtitle': 'المساحة المحددة: {{area}} كم²',
|
|
9498
|
+
'regionForm.name': 'اسم المنطقة',
|
|
9499
|
+
'regionForm.namePlaceholder': 'مثال: وسط المدينة',
|
|
9500
|
+
'regionForm.minZoom': 'أدنى تكبير',
|
|
9501
|
+
'regionForm.maxZoom': 'أقصى تكبير',
|
|
9502
|
+
'regionForm.styleUrl': 'رابط النمط',
|
|
9503
|
+
'regionForm.provider': 'مزود النمط',
|
|
9504
|
+
'regionForm.providerAuto': 'اكتشاف تلقائي',
|
|
9505
|
+
'regionForm.providerMapbox': 'Mapbox GL (يتطلب رمز الوصول)',
|
|
9506
|
+
'regionForm.providerMaplibre': 'MapLibre GL (مفتوح المصدر)',
|
|
9507
|
+
'regionForm.providerInfo': 'سيقوم الاكتشاف التلقائي بتحليل رابط النمط لتحديد المزود',
|
|
9508
|
+
'regionForm.accessToken': 'رمز وصول Mapbox',
|
|
9509
|
+
'regionForm.accessTokenRequired': 'مطلوب',
|
|
9510
|
+
'regionForm.accessTokenHelp': 'احصل على رمز الوصول من',
|
|
9511
|
+
'regionForm.downloadRegion': 'تحميل المنطقة',
|
|
9512
|
+
'regionForm.area': 'المساحة',
|
|
9513
|
+
'regionForm.bounds': 'الحدود',
|
|
9514
|
+
// Region List - قائمة المناطق
|
|
9515
|
+
'regionList.empty': 'لم يتم العثور على أنماط أو مناطق غير متصلة. انقر على "إضافة منطقة" للبدء.',
|
|
9516
|
+
'regionList.emptyFallback': 'لم يتم العثور على مناطق غير متصلة. انقر على "إضافة منطقة" للبدء.',
|
|
9517
|
+
'regionList.orphanedHeader': 'مناطق بدون نمط',
|
|
9518
|
+
'regionList.orphanedDescription': '{{count}} منطقة غير مرتبطة بأي نمط',
|
|
9519
|
+
'regionList.zoom': 'التكبير',
|
|
9520
|
+
'regionList.downloaded': 'تم التحميل',
|
|
9521
|
+
'regionList.size': 'الحجم',
|
|
9522
|
+
'regionList.styleId': 'معرف النمط',
|
|
9523
|
+
'regionList.storedStyleSize': 'حجم النمط المخزن',
|
|
9524
|
+
// Region Details - تفاصيل المنطقة
|
|
9525
|
+
'regionDetails.title': 'تفاصيل المنطقة',
|
|
9526
|
+
'regionDetails.focusOnMap': 'التركيز على الخريطة',
|
|
9527
|
+
// Style - النمط
|
|
9528
|
+
'style.unnamedStyle': 'نمط بدون اسم',
|
|
9529
|
+
'style.regions': '{{count}} منطقة',
|
|
9530
|
+
// Delete Confirmation - تأكيد الحذف
|
|
9531
|
+
'delete.regionTitle': 'حذف المنطقة',
|
|
9532
|
+
'delete.regionMessage': 'هل أنت متأكد من حذف المنطقة "{{name}}"؟ لا يمكن التراجع عن هذا الإجراء.',
|
|
9533
|
+
'delete.styleTitle': 'حذف النمط',
|
|
9534
|
+
'delete.styleMessage': 'هل أنت متأكد من حذف النمط "{{name}}"؟ لا يمكن التراجع عن هذا الإجراء وسيؤثر على المناطق المرتبطة.',
|
|
9535
|
+
// Re-download - إعادة التحميل
|
|
9536
|
+
'redownload.title': 'إعادة تحميل المنطقة',
|
|
9537
|
+
'redownload.message': 'إعادة تحميل "{{name}}"؟\n\nسيتم:\n• حذف البلاطات والموارد الحالية\n• إعادة تحميل جميع البيانات بالإعدادات الحالية\n• إصلاح أي موارد تالفة أو قديمة',
|
|
9538
|
+
'redownload.button': 'إعادة التحميل',
|
|
9539
|
+
// Fix Tiles - إصلاح البلاطات
|
|
9540
|
+
'fixTiles.noIssuesTitle': 'لم يتم العثور على مشاكل',
|
|
9541
|
+
'fixTiles.noIssuesMessage': 'لم يتم اكتشاف بلاطات مضغوطة. البلاطات بالتنسيق الصحيح!',
|
|
9542
|
+
'fixTiles.title': 'إصلاح البلاطات المضغوطة',
|
|
9543
|
+
'fixTiles.message': 'تم العثور على {{count}} بلاطة مضغوطة قد تسبب أخطاء في العرض.\n\nسيتم:\n1. إزالة جميع البلاطات المضغوطة ({{count}} بلاطة)\n2. ستحتاج إلى إعادة تحميل المناطق للحصول على بلاطات مفكوكة الضغط\n\nهل تريد المتابعة؟',
|
|
9544
|
+
'fixTiles.button': 'إصلاح البلاطات',
|
|
9545
|
+
'fixTiles.completeTitle': 'اكتمل التنظيف',
|
|
9546
|
+
'fixTiles.completeMessage': 'تم إزالة {{count}} بلاطة مضغوطة بنجاح.\n\nيرجى إعادة تحميل مناطقك للحصول على البلاطات المصححة.',
|
|
9547
|
+
// Download Progress - تقدم التحميل
|
|
9548
|
+
'download.active': 'التحميلات النشطة',
|
|
9549
|
+
'download.phase.style': 'النمط',
|
|
9550
|
+
'download.phase.sprites': 'الرموز',
|
|
9551
|
+
'download.phase.glyphs': 'الخطوط',
|
|
9552
|
+
'download.phase.tiles': 'البلاطات',
|
|
9553
|
+
// Style Selection - اختيار النمط
|
|
9554
|
+
'styleSelection.title': 'اختر نمط غير متصل',
|
|
9555
|
+
'styleSelection.message': 'اختر النمط غير المتصل الذي تريد تحميله:',
|
|
9556
|
+
'styleSelection.sources': 'مصادر',
|
|
9557
|
+
// Import/Export - استيراد/تصدير
|
|
9558
|
+
'importExport.title': 'استيراد/تصدير',
|
|
9559
|
+
'importExport.export': 'تصدير',
|
|
9560
|
+
'importExport.import': 'استيراد',
|
|
9561
|
+
// Errors - الأخطاء
|
|
9562
|
+
'error.loadingContent': 'خطأ في تحميل المحتوى',
|
|
9563
|
+
'error.tryAgain': 'يرجى المحاولة مرة أخرى',
|
|
9564
|
+
'error.mapNotInitialized': 'الخريطة غير مهيأة. يرجى التأكد من تحميل الخريطة قبل تحميل النمط.',
|
|
9565
|
+
'error.invalidStyleData': 'بيانات نمط غير صالحة. قد يكون النمط تالفاً.',
|
|
9566
|
+
'error.styleMissingSources': 'النمط لا يحتوي على مصادر. قد يكون النمط غير مكتمل.',
|
|
9567
|
+
'error.styleMissingLayers': 'النمط لا يحتوي على طبقات. قد يكون النمط غير مكتمل.',
|
|
9568
|
+
'error.regionNotFound': 'المنطقة غير موجودة',
|
|
9569
|
+
'error.downloadFailed': 'فشل التحميل',
|
|
9570
|
+
'error.deleteFailed': 'فشل الحذف',
|
|
9571
|
+
'error.loadStyleFailed': 'فشل تحميل النمط',
|
|
9572
|
+
// Warnings - التحذيرات
|
|
9573
|
+
'warning.compressedTiles': 'تم العثور على {{count}} بلاطة مضغوطة قد تسبب مشاكل في العرض.',
|
|
9574
|
+
'warning.compressedTilesRecommendation': 'موصى به: استخدم زر "إعادة التحميل" لإصلاح هذه المشكلة.',
|
|
9575
|
+
'warning.continueAnyway': 'هل تريد الاستمرار في تحميل النمط على أي حال؟',
|
|
9576
|
+
// Drawing Tool - أداة الرسم
|
|
9577
|
+
'drawing.instructions': 'انقر واسحب لرسم منطقة',
|
|
9578
|
+
'drawing.cancel': 'اضغط ESC للإلغاء',
|
|
9579
|
+
// Offline Maps Button - زر الخرائط غير المتصلة
|
|
9580
|
+
'button.offlineMaps': 'خرائط غير متصلة',
|
|
9581
|
+
// Region Details Modal - نافذة تفاصيل المنطقة
|
|
9582
|
+
'regionDetails.bounds': 'الحدود',
|
|
9583
|
+
'regionDetails.zoomRange': 'نطاق التكبير',
|
|
9584
|
+
'regionDetails.created': 'تاريخ الإنشاء',
|
|
9585
|
+
// Import/Export Modal - نافذة الاستيراد/التصدير
|
|
9586
|
+
'importExport.regionTitle': 'استيراد/تصدير المنطقة',
|
|
9587
|
+
'importExport.regionInfo': 'معلومات المنطقة',
|
|
9588
|
+
'importExport.id': 'المعرف',
|
|
9589
|
+
'importExport.name': 'الاسم',
|
|
9590
|
+
'importExport.unnamed': 'بدون اسم',
|
|
9591
|
+
'importExport.zoom': 'التكبير',
|
|
9592
|
+
'importExport.created': 'تاريخ الإنشاء',
|
|
9593
|
+
'importExport.exportRegion': 'تصدير المنطقة',
|
|
9594
|
+
'importExport.exportFormat': 'تنسيق التصدير',
|
|
9595
|
+
'importExport.formatJson': 'JSON - بيانات كاملة (موصى به)',
|
|
9596
|
+
'importExport.formatPmtiles': 'PMTiles - بلاطات محسنة للويب',
|
|
9597
|
+
'importExport.formatMbtiles': 'MBTiles - معيار الصناعة',
|
|
9598
|
+
'importExport.formatHint': 'اختر التنسيق بناءً على حالة استخدامك',
|
|
9599
|
+
'importExport.includeComponents': 'تضمين المكونات',
|
|
9600
|
+
'importExport.styleConfig': 'إعدادات النمط',
|
|
9601
|
+
'importExport.mapTiles': 'بلاطات الخريطة',
|
|
9602
|
+
'importExport.spritesIcons': 'الرموز والأيقونات',
|
|
9603
|
+
'importExport.fontsGlyphs': 'الخطوط والحروف',
|
|
9604
|
+
'importExport.preparingExport': 'جاري تجهيز التصدير...',
|
|
9605
|
+
'importExport.exportComplete': 'اكتمل التصدير!',
|
|
9606
|
+
'importExport.exportFailed': 'فشل التصدير. يرجى المحاولة مرة أخرى.',
|
|
9607
|
+
'importExport.importRegion': 'استيراد المنطقة',
|
|
9608
|
+
'importExport.selectFile': 'اختر ملف',
|
|
9609
|
+
'importExport.fileFormatsHint': 'يدعم تنسيقات JSON و PMTiles و MBTiles',
|
|
9610
|
+
'importExport.newRegionName': 'اسم المنطقة الجديد (اختياري)',
|
|
9611
|
+
'importExport.newRegionNamePlaceholder': 'اتركه فارغاً لاستخدام الاسم الأصلي',
|
|
9612
|
+
'importExport.overwriteIfExists': 'الكتابة فوق المنطقة الموجودة',
|
|
9613
|
+
'importExport.preparingImport': 'جاري تجهيز الاستيراد...',
|
|
9614
|
+
'importExport.importComplete': 'اكتمل الاستيراد!',
|
|
9615
|
+
'importExport.importFailed': 'فشل الاستيراد. يرجى المحاولة مرة أخرى.',
|
|
9616
|
+
'importExport.formatGuide': 'دليل التنسيقات',
|
|
9617
|
+
'importExport.jsonDesc': 'بيانات كاملة، قابلة للقراءة، الأفضل للتطوير',
|
|
9618
|
+
'importExport.pmtilesDesc': 'محسن للويب، خدمة فعالة، متوافق مع السحابة',
|
|
9619
|
+
'importExport.mbtilesDesc': 'معيار الصناعة، قائم على SQLite، متعدد المنصات',
|
|
9620
|
+
// Active Downloads - التحميلات النشطة
|
|
9621
|
+
'download.activeCount': 'التحميلات النشطة ({{count}})',
|
|
9622
|
+
// Panel Manager additional strings - سلاسل إضافية للوحة
|
|
9623
|
+
'panel.downloadedRegions': 'المناطق المحملة',
|
|
9624
|
+
'panel.noExpiry': 'بدون انتهاء',
|
|
9625
|
+
'panel.noBounds': 'بدون حدود',
|
|
9626
|
+
'panel.sources': 'مصادر',
|
|
9627
|
+
// Empty/Error States - حالات الفراغ/الخطأ
|
|
9628
|
+
'emptyState.title': 'لا توجد عناصر',
|
|
9629
|
+
'emptyState.message': 'لا توجد عناصر للعرض',
|
|
9630
|
+
'errorState.message': 'حدث خطأ ما',
|
|
9631
|
+
'errorState.retry': 'حاول مرة أخرى',
|
|
9632
|
+
// List - القائمة
|
|
9633
|
+
'list.empty': 'لا توجد عناصر للعرض',
|
|
9634
|
+
'list.noItemsFound': 'لم يتم العثور على عناصر',
|
|
9635
|
+
// Toast - الإشعارات
|
|
9636
|
+
'toast.dismiss': 'إغلاق الإشعار',
|
|
9637
|
+
// Control Buttons - أزرار التحكم
|
|
9638
|
+
'control.offlineMapManager': 'مدير الخرائط غير المتصلة',
|
|
9639
|
+
'control.cancelSelection': 'إلغاء التحديد',
|
|
9640
|
+
'control.saveSelectedRegion': 'حفظ المنطقة المحددة',
|
|
9641
|
+
// Alerts/Messages - التنبيهات/الرسائل
|
|
9642
|
+
'alert.unknownError': 'حدث خطأ غير معروف',
|
|
9643
|
+
'alert.downloadNotImplemented': 'ميزة التحميل غير مطبقة في هذه النسخة',
|
|
9644
|
+
'alert.mapNotAvailable': 'الخريطة غير متاحة',
|
|
9645
|
+
'alert.invalidBounds': 'حدود غير صالحة للمنطقة',
|
|
9646
|
+
'alert.styleNotFound': 'النمط غير موجود',
|
|
9647
|
+
'alert.noStylesFound': 'لم يتم العثور على أنماط في IndexedDB',
|
|
9648
|
+
'alert.failedToApplyStyle': 'فشل تطبيق النمط على الخريطة',
|
|
9649
|
+
'alert.failedToLoadStyle': 'فشل تحميل النمط',
|
|
9650
|
+
'alert.failedToRedownload': 'فشل إعادة تحميل المنطقة',
|
|
9651
|
+
'alert.downloadManagerNotAvailable': 'مدير التحميل غير متاح',
|
|
9652
|
+
// Download Manager - مدير التحميل
|
|
9653
|
+
'downloadManager.downloadingStyle': 'جاري تحميل النمط',
|
|
9654
|
+
'downloadManager.downloadingSprites': 'جاري تحميل الرموز',
|
|
9655
|
+
'downloadManager.downloadingGlyphs': 'جاري تحميل الخطوط',
|
|
9656
|
+
'downloadManager.downloadingTiles': 'جاري تحميل البلاطات',
|
|
9657
|
+
'downloadManager.styleExistsNotFound': 'النمط موجود لكن لم يتم العثور عليه',
|
|
9658
|
+
'downloadManager.styleNotFoundForResources': 'لم يتم العثور على النمط لتحميل الموارد',
|
|
9659
|
+
'downloadManager.styleNotFoundForTiles': 'لم يتم العثور على النمط لتحميل البلاطات',
|
|
9660
|
+
'downloadManager.styleNoSources': 'النمط لا يحتوي على مصادر لتحميل البلاطات',
|
|
9661
|
+
// Form Validation - التحقق من النموذج
|
|
9662
|
+
'validation.required': 'هذا الحقل مطلوب',
|
|
9663
|
+
'validation.invalidFormat': 'تنسيق غير صالح',
|
|
9664
|
+
'validation.mustBeNumber': 'يجب أن يكون رقماً',
|
|
9665
|
+
'validation.mustBeWholeNumber': 'يجب أن يكون رقماً صحيحاً',
|
|
9666
|
+
'validation.invalidUrl': 'تنسيق رابط غير صالح',
|
|
9667
|
+
};
|
|
9668
|
+
|
|
9669
|
+
/**
|
|
9670
|
+
* Internationalization (i18n) module for map-gl-offline
|
|
9671
|
+
* Uses i18next for multi-language support
|
|
9672
|
+
*
|
|
9673
|
+
* Currently supports: English (en), Arabic (ar)
|
|
9674
|
+
*/
|
|
9675
|
+
// RTL languages
|
|
9676
|
+
const rtlLanguages = ['ar'];
|
|
9677
|
+
// Initialize i18next
|
|
9678
|
+
instance.init({
|
|
9679
|
+
lng: localStorage.getItem('offline-manager-language') || 'en',
|
|
9680
|
+
fallbackLng: 'en',
|
|
9681
|
+
debug: false,
|
|
9682
|
+
interpolation: {
|
|
9683
|
+
escapeValue: false, // Not needed for non-HTML contexts
|
|
9684
|
+
prefix: '{{',
|
|
9685
|
+
suffix: '}}',
|
|
9686
|
+
},
|
|
9687
|
+
resources: {
|
|
9688
|
+
en: { translation: en },
|
|
9689
|
+
ar: { translation: ar },
|
|
9690
|
+
},
|
|
9691
|
+
});
|
|
9692
|
+
/**
|
|
9693
|
+
* I18n Manager wrapper for i18next with additional functionality
|
|
9694
|
+
*/
|
|
9695
|
+
class I18nManager {
|
|
9696
|
+
listeners = new Set();
|
|
6940
9697
|
/**
|
|
6941
|
-
* Get the
|
|
9698
|
+
* Get the current language
|
|
6942
9699
|
*/
|
|
6943
|
-
|
|
6944
|
-
return
|
|
9700
|
+
getLanguage() {
|
|
9701
|
+
return instance.language;
|
|
6945
9702
|
}
|
|
6946
9703
|
/**
|
|
6947
|
-
*
|
|
9704
|
+
* Set the current language
|
|
6948
9705
|
*/
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
this.
|
|
6954
|
-
//
|
|
6955
|
-
if (mode === 'dark') {
|
|
6956
|
-
document.documentElement.classList.add('dark');
|
|
6957
|
-
}
|
|
6958
|
-
else {
|
|
6959
|
-
document.documentElement.classList.remove('dark');
|
|
6960
|
-
}
|
|
9706
|
+
setLanguage(lang) {
|
|
9707
|
+
instance.changeLanguage(lang);
|
|
9708
|
+
localStorage.setItem('offline-manager-language', lang);
|
|
9709
|
+
// Update document direction for RTL languages
|
|
9710
|
+
this.updateDocumentDirection();
|
|
9711
|
+
// Notify listeners
|
|
6961
9712
|
this.notifyListeners();
|
|
6962
9713
|
}
|
|
6963
9714
|
/**
|
|
6964
|
-
*
|
|
9715
|
+
* Check if current language is RTL
|
|
6965
9716
|
*/
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
if (preference === 'system') {
|
|
6969
|
-
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
6970
|
-
this.applyThemeMode(systemPrefersDark ? 'dark' : 'light', 'system');
|
|
6971
|
-
}
|
|
6972
|
-
else {
|
|
6973
|
-
this.applyThemeMode(preference, preference);
|
|
6974
|
-
}
|
|
9717
|
+
isRTL() {
|
|
9718
|
+
return rtlLanguages.includes(this.getLanguage());
|
|
6975
9719
|
}
|
|
6976
9720
|
/**
|
|
6977
|
-
*
|
|
6978
|
-
* @deprecated Use setThemePreference instead
|
|
9721
|
+
* Update document direction based on current language
|
|
6979
9722
|
*/
|
|
6980
|
-
|
|
6981
|
-
|
|
9723
|
+
updateDocumentDirection() {
|
|
9724
|
+
const offlineManagerElements = document.querySelectorAll('.offline-manager-control');
|
|
9725
|
+
offlineManagerElements.forEach(el => {
|
|
9726
|
+
if (this.isRTL()) {
|
|
9727
|
+
el.setAttribute('dir', 'rtl');
|
|
9728
|
+
el.classList.add('rtl');
|
|
9729
|
+
}
|
|
9730
|
+
else {
|
|
9731
|
+
el.setAttribute('dir', 'ltr');
|
|
9732
|
+
el.classList.remove('rtl');
|
|
9733
|
+
}
|
|
9734
|
+
});
|
|
6982
9735
|
}
|
|
6983
9736
|
/**
|
|
6984
|
-
*
|
|
9737
|
+
* Get a translated string using i18next
|
|
6985
9738
|
*/
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
if (preference === 'light') {
|
|
6989
|
-
this.setThemePreference('dark');
|
|
6990
|
-
}
|
|
6991
|
-
else if (preference === 'dark') {
|
|
6992
|
-
this.setThemePreference('system');
|
|
6993
|
-
}
|
|
6994
|
-
else {
|
|
6995
|
-
this.setThemePreference('light');
|
|
6996
|
-
}
|
|
9739
|
+
t(key, replacements) {
|
|
9740
|
+
return instance.t(key, replacements);
|
|
6997
9741
|
}
|
|
6998
9742
|
/**
|
|
6999
|
-
*
|
|
7000
|
-
* @deprecated Use cycleTheme for light/dark/system cycling
|
|
9743
|
+
* Get all available languages
|
|
7001
9744
|
*/
|
|
7002
|
-
|
|
7003
|
-
|
|
9745
|
+
getAvailableLanguages() {
|
|
9746
|
+
return [
|
|
9747
|
+
{ code: 'en', name: 'English', nativeName: 'English' },
|
|
9748
|
+
{ code: 'ar', name: 'Arabic', nativeName: 'العربية' },
|
|
9749
|
+
];
|
|
7004
9750
|
}
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
}
|
|
7012
|
-
};
|
|
9751
|
+
/**
|
|
9752
|
+
* Subscribe to language changes
|
|
9753
|
+
*/
|
|
9754
|
+
subscribe(callback) {
|
|
9755
|
+
this.listeners.add(callback);
|
|
9756
|
+
return () => this.listeners.delete(callback);
|
|
7013
9757
|
}
|
|
9758
|
+
/**
|
|
9759
|
+
* Notify all listeners of language change
|
|
9760
|
+
*/
|
|
7014
9761
|
notifyListeners() {
|
|
7015
|
-
this.listeners.forEach(
|
|
9762
|
+
this.listeners.forEach(callback => callback());
|
|
9763
|
+
}
|
|
9764
|
+
/**
|
|
9765
|
+
* Get the i18next instance for advanced usage
|
|
9766
|
+
*/
|
|
9767
|
+
getInstance() {
|
|
9768
|
+
return instance;
|
|
7016
9769
|
}
|
|
7017
9770
|
}
|
|
7018
|
-
//
|
|
7019
|
-
const
|
|
9771
|
+
// Singleton instance
|
|
9772
|
+
const i18n = new I18nManager();
|
|
9773
|
+
// Convenience function for translations
|
|
9774
|
+
const t = (key, replacements) => {
|
|
9775
|
+
return i18n.t(key, replacements);
|
|
9776
|
+
};
|
|
7020
9777
|
|
|
7021
9778
|
/**
|
|
7022
9779
|
* Base Component Class
|
|
@@ -7368,7 +10125,7 @@ class ButtonManager {
|
|
|
7368
10125
|
const buttonConfig = {
|
|
7369
10126
|
className: 'maplibregl-ctrl-icon relative',
|
|
7370
10127
|
icon: icons.cloud({ size: 20, color: 'black' }),
|
|
7371
|
-
title: '
|
|
10128
|
+
title: t('control.offlineMapManager'),
|
|
7372
10129
|
showProgressBadge: true,
|
|
7373
10130
|
onClick: this.options.onTogglePanel,
|
|
7374
10131
|
};
|
|
@@ -7476,13 +10233,13 @@ class Modal extends BaseComponent {
|
|
|
7476
10233
|
actions.className = 'flex items-center gap-2';
|
|
7477
10234
|
// Theme toggle button
|
|
7478
10235
|
if (this.config.showThemeToggle && this.config.onThemeToggle) {
|
|
7479
|
-
const themeButton = this.createIconButton(`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>`, '
|
|
10236
|
+
const themeButton = this.createIconButton(`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>`, t('theme.toggle'));
|
|
7480
10237
|
themeButton.addEventListener('click', this.config.onThemeToggle);
|
|
7481
10238
|
actions.appendChild(themeButton);
|
|
7482
10239
|
}
|
|
7483
10240
|
// Close button
|
|
7484
10241
|
if (this.config.closable && this.config.onClose) {
|
|
7485
|
-
const closeButton = this.createIconButton(`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`, '
|
|
10242
|
+
const closeButton = this.createIconButton(`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>`, t('app.close'));
|
|
7486
10243
|
closeButton.addEventListener('click', this.config.onClose);
|
|
7487
10244
|
actions.appendChild(closeButton);
|
|
7488
10245
|
}
|
|
@@ -7598,7 +10355,7 @@ class RegionDetailsModal {
|
|
|
7598
10355
|
show() {
|
|
7599
10356
|
const { region } = this.options;
|
|
7600
10357
|
const modalConfig = {
|
|
7601
|
-
title: '
|
|
10358
|
+
title: t('regionDetails.title'),
|
|
7602
10359
|
size: 'sm',
|
|
7603
10360
|
closable: true,
|
|
7604
10361
|
showThemeToggle: false,
|
|
@@ -7621,39 +10378,42 @@ class RegionDetailsModal {
|
|
|
7621
10378
|
createContent(region) {
|
|
7622
10379
|
const content = document.createElement('div');
|
|
7623
10380
|
content.className = 'flex flex-col gap-5';
|
|
10381
|
+
if (i18n.isRTL()) {
|
|
10382
|
+
content.setAttribute('dir', 'rtl');
|
|
10383
|
+
}
|
|
7624
10384
|
content.innerHTML = `
|
|
7625
10385
|
<div class="glass-input p-4 rounded-xl border-0 bg-gray-50/50 dark:bg-gray-800/50">
|
|
7626
10386
|
<h3 class="m-0 text-xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-gray-900 to-gray-600 dark:from-white dark:to-gray-300">
|
|
7627
10387
|
${region.name}
|
|
7628
10388
|
</h3>
|
|
7629
10389
|
</div>
|
|
7630
|
-
|
|
10390
|
+
|
|
7631
10391
|
<div class="grid grid-cols-2 gap-4">
|
|
7632
10392
|
<div class="glass-input p-4 rounded-xl border-0 bg-gray-50/30 dark:bg-gray-800/30">
|
|
7633
10393
|
<label class="block mb-1.5 text-xs font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
|
7634
|
-
|
|
10394
|
+
${t('regionDetails.bounds')}
|
|
7635
10395
|
</label>
|
|
7636
10396
|
<div class="text-sm font-mono text-gray-700 dark:text-gray-300 leading-relaxed">
|
|
7637
10397
|
${region.bounds[0][1].toFixed(4)}, ${region.bounds[0][0].toFixed(4)}<br>
|
|
7638
10398
|
${region.bounds[1][1].toFixed(4)}, ${region.bounds[1][0].toFixed(4)}
|
|
7639
10399
|
</div>
|
|
7640
10400
|
</div>
|
|
7641
|
-
|
|
10401
|
+
|
|
7642
10402
|
<div class="glass-input p-4 rounded-xl border-0 bg-gray-50/30 dark:bg-gray-800/30">
|
|
7643
10403
|
<label class="block mb-1.5 text-xs font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
|
7644
|
-
|
|
10404
|
+
${t('regionDetails.zoomRange')}
|
|
7645
10405
|
</label>
|
|
7646
10406
|
<div class="text-lg font-semibold text-gray-900 dark:text-white">
|
|
7647
10407
|
${region.minZoom} - ${region.maxZoom}
|
|
7648
10408
|
</div>
|
|
7649
10409
|
</div>
|
|
7650
10410
|
</div>
|
|
7651
|
-
|
|
10411
|
+
|
|
7652
10412
|
${region.created
|
|
7653
10413
|
? `
|
|
7654
10414
|
<div class="glass-input p-4 rounded-xl border-0 bg-gray-50/30 dark:bg-gray-800/30">
|
|
7655
10415
|
<label class="block mb-1.5 text-xs font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400">
|
|
7656
|
-
|
|
10416
|
+
${t('regionDetails.created')}
|
|
7657
10417
|
</label>
|
|
7658
10418
|
<div class="text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
7659
10419
|
${formatDate(region.created)}
|
|
@@ -7670,16 +10430,19 @@ class RegionDetailsModal {
|
|
|
7670
10430
|
createFooter() {
|
|
7671
10431
|
const footer = document.createElement('div');
|
|
7672
10432
|
footer.className = 'flex gap-2 justify-end';
|
|
10433
|
+
if (i18n.isRTL()) {
|
|
10434
|
+
footer.setAttribute('dir', 'rtl');
|
|
10435
|
+
}
|
|
7673
10436
|
// Focus button
|
|
7674
10437
|
const focusButton = new Button({
|
|
7675
|
-
text: '
|
|
10438
|
+
text: t('regionDetails.focusOnMap'),
|
|
7676
10439
|
variant: 'secondary',
|
|
7677
10440
|
icon: icons.focus({ size: 16, color: 'currentColor' }),
|
|
7678
10441
|
onClick: () => this.focusRegion(),
|
|
7679
10442
|
});
|
|
7680
10443
|
// Close button
|
|
7681
10444
|
const closeButton = new Button({
|
|
7682
|
-
text: '
|
|
10445
|
+
text: t('app.close'),
|
|
7683
10446
|
variant: 'primary',
|
|
7684
10447
|
onClick: () => this.close(),
|
|
7685
10448
|
});
|
|
@@ -7806,7 +10569,7 @@ class ImportExportModal {
|
|
|
7806
10569
|
}
|
|
7807
10570
|
show() {
|
|
7808
10571
|
const modalConfig = {
|
|
7809
|
-
title: '
|
|
10572
|
+
title: t('importExport.regionTitle'),
|
|
7810
10573
|
subtitle: this.options.region.name || this.options.region.id,
|
|
7811
10574
|
size: 'md',
|
|
7812
10575
|
closable: true,
|
|
@@ -7830,6 +10593,9 @@ class ImportExportModal {
|
|
|
7830
10593
|
createContent() {
|
|
7831
10594
|
const content = document.createElement('div');
|
|
7832
10595
|
content.className = 'flex flex-col gap-6';
|
|
10596
|
+
if (i18n.isRTL()) {
|
|
10597
|
+
content.setAttribute('dir', 'rtl');
|
|
10598
|
+
}
|
|
7833
10599
|
// Region Info Card
|
|
7834
10600
|
const infoCard = this.createRegionInfoCard();
|
|
7835
10601
|
content.appendChild(infoCard);
|
|
@@ -7854,23 +10620,23 @@ class ImportExportModal {
|
|
|
7854
10620
|
card.innerHTML = `
|
|
7855
10621
|
<h4 class="text-sm font-bold uppercase tracking-wider text-gray-900 dark:text-white mb-4 flex items-center gap-2">
|
|
7856
10622
|
${icons.mapPin({ size: 16, color: 'currentColor' })}
|
|
7857
|
-
|
|
10623
|
+
${t('importExport.regionInfo')}
|
|
7858
10624
|
</h4>
|
|
7859
10625
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
7860
10626
|
<div class="p-3 rounded-lg bg-white/40 dark:bg-black/20">
|
|
7861
|
-
<span class="font-medium text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide"
|
|
10627
|
+
<span class="font-medium text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">${t('importExport.id')}</span>
|
|
7862
10628
|
<div class="text-gray-900 dark:text-white font-mono text-xs break-all mt-1">${this.options.region.id}</div>
|
|
7863
10629
|
</div>
|
|
7864
10630
|
<div class="p-3 rounded-lg bg-white/40 dark:bg-black/20">
|
|
7865
|
-
<span class="font-medium text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide"
|
|
7866
|
-
<div class="text-gray-900 dark:text-white font-medium mt-1">${this.options.region.name || '
|
|
10631
|
+
<span class="font-medium text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">${t('importExport.name')}</span>
|
|
10632
|
+
<div class="text-gray-900 dark:text-white font-medium mt-1">${this.options.region.name || t('importExport.unnamed')}</div>
|
|
7867
10633
|
</div>
|
|
7868
10634
|
<div class="p-3 rounded-lg bg-white/40 dark:bg-black/20">
|
|
7869
|
-
<span class="font-medium text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide"
|
|
10635
|
+
<span class="font-medium text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">${t('importExport.zoom')}</span>
|
|
7870
10636
|
<div class="text-gray-900 dark:text-white font-medium mt-1">Z${this.options.region.minZoom}-${this.options.region.maxZoom}</div>
|
|
7871
10637
|
</div>
|
|
7872
10638
|
<div class="p-3 rounded-lg bg-white/40 dark:bg-black/20">
|
|
7873
|
-
<span class="font-medium text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide"
|
|
10639
|
+
<span class="font-medium text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wide">${t('importExport.created')}</span>
|
|
7874
10640
|
<div class="text-gray-900 dark:text-white font-medium mt-1">${new Date(this.options.region.created).toLocaleDateString()}</div>
|
|
7875
10641
|
</div>
|
|
7876
10642
|
</div>
|
|
@@ -7883,7 +10649,7 @@ class ImportExportModal {
|
|
|
7883
10649
|
'glass-input p-6 rounded-xl border-0 bg-white/40 dark:bg-gray-800/40 relative overflow-hidden group hover:scale-[1.01] transition-transform duration-300';
|
|
7884
10650
|
// Gradient accent
|
|
7885
10651
|
const accent = document.createElement('div');
|
|
7886
|
-
accent.className =
|
|
10652
|
+
accent.className = `absolute top-0 ${i18n.isRTL() ? 'right-0' : 'left-0'} w-1 h-full bg-blue-500 opacity-50`;
|
|
7887
10653
|
section.appendChild(accent);
|
|
7888
10654
|
const header = document.createElement('h3');
|
|
7889
10655
|
header.className =
|
|
@@ -7892,7 +10658,7 @@ class ImportExportModal {
|
|
|
7892
10658
|
<div class="p-2 bg-blue-500/10 rounded-lg text-blue-600 dark:text-blue-400">
|
|
7893
10659
|
${icons.upload({ size: 20, color: 'currentColor' })}
|
|
7894
10660
|
</div>
|
|
7895
|
-
|
|
10661
|
+
${t('importExport.exportRegion')}
|
|
7896
10662
|
`;
|
|
7897
10663
|
section.appendChild(header);
|
|
7898
10664
|
const formContainer = document.createElement('div');
|
|
@@ -7901,18 +10667,18 @@ class ImportExportModal {
|
|
|
7901
10667
|
const formatGroup = document.createElement('div');
|
|
7902
10668
|
const formatLabel = document.createElement('label');
|
|
7903
10669
|
formatLabel.className = 'block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2';
|
|
7904
|
-
formatLabel.textContent = '
|
|
10670
|
+
formatLabel.textContent = t('importExport.exportFormat');
|
|
7905
10671
|
this.exportFormatSelect = document.createElement('select');
|
|
7906
10672
|
this.exportFormatSelect.className =
|
|
7907
10673
|
'w-full px-4 py-3 rounded-xl text-sm glass-input text-gray-900 dark:text-white bg-white/50 dark:bg-gray-700/50 focus:outline-none focus:ring-2 focus:ring-blue-500/50 transition-all';
|
|
7908
10674
|
this.exportFormatSelect.innerHTML = `
|
|
7909
|
-
<option value="json"
|
|
7910
|
-
<option value="pmtiles"
|
|
7911
|
-
<option value="mbtiles"
|
|
10675
|
+
<option value="json">${t('importExport.formatJson')}</option>
|
|
10676
|
+
<option value="pmtiles">${t('importExport.formatPmtiles')}</option>
|
|
10677
|
+
<option value="mbtiles">${t('importExport.formatMbtiles')}</option>
|
|
7912
10678
|
`;
|
|
7913
10679
|
const formatHint = document.createElement('p');
|
|
7914
10680
|
formatHint.className = 'mt-2 text-xs text-gray-500 dark:text-gray-400 ml-1';
|
|
7915
|
-
formatHint.textContent = '
|
|
10681
|
+
formatHint.textContent = t('importExport.formatHint');
|
|
7916
10682
|
formatGroup.appendChild(formatLabel);
|
|
7917
10683
|
formatGroup.appendChild(this.exportFormatSelect);
|
|
7918
10684
|
formatGroup.appendChild(formatHint);
|
|
@@ -7921,7 +10687,7 @@ class ImportExportModal {
|
|
|
7921
10687
|
const optionsGroup = document.createElement('div');
|
|
7922
10688
|
const optionsLabel = document.createElement('label');
|
|
7923
10689
|
optionsLabel.className = 'block text-sm font-medium text-gray-700 dark:text-gray-300 mb-3';
|
|
7924
|
-
optionsLabel.textContent = '
|
|
10690
|
+
optionsLabel.textContent = t('importExport.includeComponents');
|
|
7925
10691
|
const checkboxContainer = document.createElement('div');
|
|
7926
10692
|
checkboxContainer.className = 'grid grid-cols-1 sm:grid-cols-2 gap-3';
|
|
7927
10693
|
const createCheckbox = (text, checked = true) => {
|
|
@@ -7940,16 +10706,16 @@ class ImportExportModal {
|
|
|
7940
10706
|
label.appendChild(span);
|
|
7941
10707
|
return { label, input };
|
|
7942
10708
|
};
|
|
7943
|
-
const styleCheck = createCheckbox('
|
|
10709
|
+
const styleCheck = createCheckbox(t('importExport.styleConfig'));
|
|
7944
10710
|
this.includeStyleCheckbox = styleCheck.input;
|
|
7945
10711
|
checkboxContainer.appendChild(styleCheck.label);
|
|
7946
|
-
const tilesCheck = createCheckbox('
|
|
10712
|
+
const tilesCheck = createCheckbox(t('importExport.mapTiles'));
|
|
7947
10713
|
this.includeTilesCheckbox = tilesCheck.input;
|
|
7948
10714
|
checkboxContainer.appendChild(tilesCheck.label);
|
|
7949
|
-
const spritesCheck = createCheckbox('
|
|
10715
|
+
const spritesCheck = createCheckbox(t('importExport.spritesIcons'));
|
|
7950
10716
|
this.includeSpritesCheckbox = spritesCheck.input;
|
|
7951
10717
|
checkboxContainer.appendChild(spritesCheck.label);
|
|
7952
|
-
const fontsCheck = createCheckbox('
|
|
10718
|
+
const fontsCheck = createCheckbox(t('importExport.fontsGlyphs'));
|
|
7953
10719
|
this.includeFontsCheckbox = fontsCheck.input;
|
|
7954
10720
|
checkboxContainer.appendChild(fontsCheck.label);
|
|
7955
10721
|
optionsGroup.appendChild(optionsLabel);
|
|
@@ -7967,13 +10733,13 @@ class ImportExportModal {
|
|
|
7967
10733
|
progressBarContainer.appendChild(this.exportProgressBar);
|
|
7968
10734
|
this.exportProgressText = document.createElement('p');
|
|
7969
10735
|
this.exportProgressText.className = 'text-sm text-gray-600 dark:text-gray-400';
|
|
7970
|
-
this.exportProgressText.textContent = '
|
|
10736
|
+
this.exportProgressText.textContent = t('importExport.preparingExport');
|
|
7971
10737
|
progressContainer.appendChild(progressBarContainer);
|
|
7972
10738
|
progressContainer.appendChild(this.exportProgressText);
|
|
7973
10739
|
formContainer.appendChild(progressContainer);
|
|
7974
10740
|
// Export Button
|
|
7975
10741
|
const exportButton = new Button({
|
|
7976
|
-
text: '
|
|
10742
|
+
text: t('importExport.exportRegion'),
|
|
7977
10743
|
variant: 'primary',
|
|
7978
10744
|
icon: icons.download({ size: 16, color: 'white' }),
|
|
7979
10745
|
className: 'w-full py-2.5 text-base shadow-lg shadow-blue-500/20', // Premium button styles
|
|
@@ -7990,7 +10756,7 @@ class ImportExportModal {
|
|
|
7990
10756
|
'glass-input p-6 rounded-xl border-0 bg-white/40 dark:bg-gray-800/40 relative overflow-hidden group hover:scale-[1.01] transition-transform duration-300';
|
|
7991
10757
|
// Gradient accent
|
|
7992
10758
|
const accent = document.createElement('div');
|
|
7993
|
-
accent.className =
|
|
10759
|
+
accent.className = `absolute top-0 ${i18n.isRTL() ? 'right-0' : 'left-0'} w-1 h-full bg-green-500 opacity-50`;
|
|
7994
10760
|
section.appendChild(accent);
|
|
7995
10761
|
const header = document.createElement('h3');
|
|
7996
10762
|
header.className =
|
|
@@ -7999,7 +10765,7 @@ class ImportExportModal {
|
|
|
7999
10765
|
<div class="p-2 bg-green-500/10 rounded-lg text-green-600 dark:text-green-400">
|
|
8000
10766
|
${icons.upload({ size: 20, color: 'currentColor' })}
|
|
8001
10767
|
</div>
|
|
8002
|
-
|
|
10768
|
+
${t('importExport.importRegion')}
|
|
8003
10769
|
`;
|
|
8004
10770
|
section.appendChild(header);
|
|
8005
10771
|
const formContainer = document.createElement('div');
|
|
@@ -8008,7 +10774,7 @@ class ImportExportModal {
|
|
|
8008
10774
|
const fileGroup = document.createElement('div');
|
|
8009
10775
|
const fileLabel = document.createElement('label');
|
|
8010
10776
|
fileLabel.className = 'block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2';
|
|
8011
|
-
fileLabel.textContent = '
|
|
10777
|
+
fileLabel.textContent = t('importExport.selectFile');
|
|
8012
10778
|
this.importFileInput = document.createElement('input');
|
|
8013
10779
|
this.importFileInput.type = 'file';
|
|
8014
10780
|
this.importFileInput.accept = '.json,.pmtiles,.mbtiles';
|
|
@@ -8016,7 +10782,7 @@ class ImportExportModal {
|
|
|
8016
10782
|
'w-full text-sm text-gray-500 file:mr-4 file:py-2.5 file:px-4 file:rounded-xl file:border-0 file:text-sm file:font-semibold file:bg-primary-50 file:text-primary-700 hover:file:bg-primary-100 dark:file:bg-primary-900/20 dark:file:text-primary-400 glass-input transition-all cursor-pointer';
|
|
8017
10783
|
const fileHint = document.createElement('p');
|
|
8018
10784
|
fileHint.className = 'mt-2 text-xs text-gray-500 dark:text-gray-400 ml-1';
|
|
8019
|
-
fileHint.textContent = '
|
|
10785
|
+
fileHint.textContent = t('importExport.fileFormatsHint');
|
|
8020
10786
|
fileGroup.appendChild(fileLabel);
|
|
8021
10787
|
fileGroup.appendChild(this.importFileInput);
|
|
8022
10788
|
fileGroup.appendChild(fileHint);
|
|
@@ -8025,10 +10791,10 @@ class ImportExportModal {
|
|
|
8025
10791
|
const nameGroup = document.createElement('div');
|
|
8026
10792
|
const nameLabel = document.createElement('label');
|
|
8027
10793
|
nameLabel.className = 'block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2';
|
|
8028
|
-
nameLabel.textContent = '
|
|
10794
|
+
nameLabel.textContent = t('importExport.newRegionName');
|
|
8029
10795
|
this.importNameInput = document.createElement('input');
|
|
8030
10796
|
this.importNameInput.type = 'text';
|
|
8031
|
-
this.importNameInput.placeholder = '
|
|
10797
|
+
this.importNameInput.placeholder = t('importExport.newRegionNamePlaceholder');
|
|
8032
10798
|
this.importNameInput.className =
|
|
8033
10799
|
'w-full px-4 py-3 rounded-xl text-sm glass-input text-gray-900 dark:text-white bg-white/50 dark:bg-gray-700/50 focus:outline-none focus:ring-2 focus:ring-green-500/50 transition-all';
|
|
8034
10800
|
nameGroup.appendChild(nameLabel);
|
|
@@ -8044,7 +10810,7 @@ class ImportExportModal {
|
|
|
8044
10810
|
'w-4 h-4 rounded border-gray-300 dark:border-gray-600 text-green-600 focus:ring-green-500 focus:ring-2 dark:bg-gray-700';
|
|
8045
10811
|
const overwriteSpan = document.createElement('span');
|
|
8046
10812
|
overwriteSpan.className = 'text-sm text-gray-700 dark:text-gray-300 font-medium';
|
|
8047
|
-
overwriteSpan.textContent = '
|
|
10813
|
+
overwriteSpan.textContent = t('importExport.overwriteIfExists');
|
|
8048
10814
|
overwriteLabel.appendChild(this.importOverwriteCheckbox);
|
|
8049
10815
|
overwriteLabel.appendChild(overwriteSpan);
|
|
8050
10816
|
formContainer.appendChild(overwriteLabel);
|
|
@@ -8060,13 +10826,13 @@ class ImportExportModal {
|
|
|
8060
10826
|
progressBarContainer.appendChild(this.importProgressBar);
|
|
8061
10827
|
this.importProgressText = document.createElement('p');
|
|
8062
10828
|
this.importProgressText.className = 'text-sm text-gray-600 dark:text-gray-400';
|
|
8063
|
-
this.importProgressText.textContent = '
|
|
10829
|
+
this.importProgressText.textContent = t('importExport.preparingImport');
|
|
8064
10830
|
progressContainer.appendChild(progressBarContainer);
|
|
8065
10831
|
progressContainer.appendChild(this.importProgressText);
|
|
8066
10832
|
formContainer.appendChild(progressContainer);
|
|
8067
10833
|
// Import Button
|
|
8068
10834
|
const importButton = new Button({
|
|
8069
|
-
text: '
|
|
10835
|
+
text: t('importExport.importRegion'),
|
|
8070
10836
|
variant: 'success', // Assuming 'success' variant exists in Button component, if not might need style adjustment. Assuming it works based on previous code.
|
|
8071
10837
|
icon: icons.upload({ size: 16, color: 'white' }),
|
|
8072
10838
|
className: 'w-full py-2.5 text-base shadow-lg shadow-green-500/20',
|
|
@@ -8084,20 +10850,20 @@ class ImportExportModal {
|
|
|
8084
10850
|
guide.innerHTML = `
|
|
8085
10851
|
<h4 class="text-sm font-bold uppercase tracking-wider text-blue-900 dark:text-blue-300 mb-3 flex items-center gap-2">
|
|
8086
10852
|
${icons.infoCircle({ size: 16, color: 'currentColor' })}
|
|
8087
|
-
|
|
10853
|
+
${t('importExport.formatGuide')}
|
|
8088
10854
|
</h4>
|
|
8089
10855
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs">
|
|
8090
10856
|
<div class="p-3 rounded-lg bg-white/50 dark:bg-black/20">
|
|
8091
10857
|
<div class="font-bold text-base text-blue-800 dark:text-blue-300 mb-1">JSON</div>
|
|
8092
|
-
<div class="text-blue-700 dark:text-blue-400 leading-relaxed"
|
|
10858
|
+
<div class="text-blue-700 dark:text-blue-400 leading-relaxed">${t('importExport.jsonDesc')}</div>
|
|
8093
10859
|
</div>
|
|
8094
10860
|
<div class="p-3 rounded-lg bg-white/50 dark:bg-black/20">
|
|
8095
10861
|
<div class="font-bold text-base text-blue-800 dark:text-blue-300 mb-1">PMTiles</div>
|
|
8096
|
-
<div class="text-blue-700 dark:text-blue-400 leading-relaxed"
|
|
10862
|
+
<div class="text-blue-700 dark:text-blue-400 leading-relaxed">${t('importExport.pmtilesDesc')}</div>
|
|
8097
10863
|
</div>
|
|
8098
10864
|
<div class="p-3 rounded-lg bg-white/50 dark:bg-black/20">
|
|
8099
10865
|
<div class="font-bold text-base text-blue-800 dark:text-blue-300 mb-1">MBTiles</div>
|
|
8100
|
-
<div class="text-blue-700 dark:text-blue-400 leading-relaxed"
|
|
10866
|
+
<div class="text-blue-700 dark:text-blue-400 leading-relaxed">${t('importExport.mbtilesDesc')}</div>
|
|
8101
10867
|
</div>
|
|
8102
10868
|
</div>
|
|
8103
10869
|
`;
|
|
@@ -8144,7 +10910,7 @@ class ImportExportModal {
|
|
|
8144
10910
|
this.exportProgressBar.style.width = '100%';
|
|
8145
10911
|
}
|
|
8146
10912
|
if (this.exportProgressText) {
|
|
8147
|
-
this.exportProgressText.textContent = '
|
|
10913
|
+
this.exportProgressText.textContent = t('importExport.exportComplete');
|
|
8148
10914
|
}
|
|
8149
10915
|
this.options.onExport?.(result);
|
|
8150
10916
|
// Hide modal after short delay
|
|
@@ -8153,7 +10919,7 @@ class ImportExportModal {
|
|
|
8153
10919
|
catch (error) {
|
|
8154
10920
|
modalLogger.error('Export error:', error instanceof Error ? error.message : String(error));
|
|
8155
10921
|
if (this.exportProgressText) {
|
|
8156
|
-
this.exportProgressText.textContent = '
|
|
10922
|
+
this.exportProgressText.textContent = t('importExport.exportFailed');
|
|
8157
10923
|
this.exportProgressText.classList.add('text-red-600', 'dark:text-red-400');
|
|
8158
10924
|
}
|
|
8159
10925
|
}
|
|
@@ -8193,7 +10959,7 @@ class ImportExportModal {
|
|
|
8193
10959
|
this.importProgressBar.style.width = '100%';
|
|
8194
10960
|
}
|
|
8195
10961
|
if (this.importProgressText) {
|
|
8196
|
-
this.importProgressText.textContent = '
|
|
10962
|
+
this.importProgressText.textContent = t('importExport.importComplete');
|
|
8197
10963
|
}
|
|
8198
10964
|
this.options.onImport?.(result);
|
|
8199
10965
|
// Hide modal after short delay
|
|
@@ -8202,7 +10968,7 @@ class ImportExportModal {
|
|
|
8202
10968
|
catch (error) {
|
|
8203
10969
|
modalLogger.error('Import error:', error instanceof Error ? error.message : String(error));
|
|
8204
10970
|
if (this.importProgressText) {
|
|
8205
|
-
this.importProgressText.textContent = '
|
|
10971
|
+
this.importProgressText.textContent = t('importExport.importFailed');
|
|
8206
10972
|
this.importProgressText.classList.add('text-red-600', 'dark:text-red-400');
|
|
8207
10973
|
}
|
|
8208
10974
|
}
|
|
@@ -8215,8 +10981,11 @@ class ImportExportModal {
|
|
|
8215
10981
|
createFooter() {
|
|
8216
10982
|
const footer = document.createElement('div');
|
|
8217
10983
|
footer.className = 'flex gap-3 justify-end';
|
|
10984
|
+
if (i18n.isRTL()) {
|
|
10985
|
+
footer.setAttribute('dir', 'rtl');
|
|
10986
|
+
}
|
|
8218
10987
|
const closeButton = new Button({
|
|
8219
|
-
text: '
|
|
10988
|
+
text: t('app.close'),
|
|
8220
10989
|
variant: 'secondary',
|
|
8221
10990
|
onClick: () => this.hide(),
|
|
8222
10991
|
});
|
|
@@ -8238,7 +11007,7 @@ class List extends BaseComponent {
|
|
|
8238
11007
|
constructor(config = {}) {
|
|
8239
11008
|
super(config);
|
|
8240
11009
|
this.config = {
|
|
8241
|
-
emptyText: '
|
|
11010
|
+
emptyText: t('list.empty'),
|
|
8242
11011
|
...config,
|
|
8243
11012
|
};
|
|
8244
11013
|
this.createListStructure();
|
|
@@ -8270,7 +11039,7 @@ class List extends BaseComponent {
|
|
|
8270
11039
|
renderEmptyState() {
|
|
8271
11040
|
const emptyElement = document.createElement('div');
|
|
8272
11041
|
emptyElement.className = 'text-center py-8 text-gray-500 dark:text-gray-400';
|
|
8273
|
-
emptyElement.textContent = this.config.emptyText || '
|
|
11042
|
+
emptyElement.textContent = this.config.emptyText || t('list.noItemsFound');
|
|
8274
11043
|
this.listContainer?.appendChild(emptyElement);
|
|
8275
11044
|
}
|
|
8276
11045
|
createItemElement(item) {
|
|
@@ -8416,6 +11185,102 @@ class List extends BaseComponent {
|
|
|
8416
11185
|
}
|
|
8417
11186
|
}
|
|
8418
11187
|
|
|
11188
|
+
/**
|
|
11189
|
+
* Language Selector Component
|
|
11190
|
+
* Provides a dropdown to switch between available languages
|
|
11191
|
+
*/
|
|
11192
|
+
class LanguageSelector extends BaseComponent {
|
|
11193
|
+
options;
|
|
11194
|
+
dropdown;
|
|
11195
|
+
isOpen = false;
|
|
11196
|
+
constructor(options = {}) {
|
|
11197
|
+
super({});
|
|
11198
|
+
this.options = options;
|
|
11199
|
+
this.render();
|
|
11200
|
+
// Subscribe to language changes
|
|
11201
|
+
i18n.subscribe(() => this.render());
|
|
11202
|
+
}
|
|
11203
|
+
createElement() {
|
|
11204
|
+
const container = document.createElement('div');
|
|
11205
|
+
container.className = 'relative';
|
|
11206
|
+
return container;
|
|
11207
|
+
}
|
|
11208
|
+
render() {
|
|
11209
|
+
const currentLang = i18n.getLanguage();
|
|
11210
|
+
const languages = i18n.getAvailableLanguages();
|
|
11211
|
+
const currentLangInfo = languages.find(l => l.code === currentLang);
|
|
11212
|
+
this.element.innerHTML = '';
|
|
11213
|
+
// Create button
|
|
11214
|
+
const button = document.createElement('button');
|
|
11215
|
+
button.className =
|
|
11216
|
+
'p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full flex items-center gap-1 transition-colors';
|
|
11217
|
+
button.title = i18n.t('language.select');
|
|
11218
|
+
button.innerHTML = `
|
|
11219
|
+
${icons.language({ size: 16, color: 'currentColor' })}
|
|
11220
|
+
<span class="text-xs font-medium">${currentLangInfo?.code.toUpperCase() || 'EN'}</span>
|
|
11221
|
+
`;
|
|
11222
|
+
button.addEventListener('click', e => {
|
|
11223
|
+
e.stopPropagation();
|
|
11224
|
+
this.toggleDropdown();
|
|
11225
|
+
});
|
|
11226
|
+
this.element.appendChild(button);
|
|
11227
|
+
// Create dropdown
|
|
11228
|
+
this.dropdown = document.createElement('div');
|
|
11229
|
+
this.dropdown.className = `
|
|
11230
|
+
absolute top-full ${i18n.isRTL() ? 'left-0' : 'right-0'} mt-1
|
|
11231
|
+
bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700
|
|
11232
|
+
rounded-lg shadow-lg overflow-hidden z-50
|
|
11233
|
+
${this.isOpen ? 'block' : 'hidden'}
|
|
11234
|
+
`;
|
|
11235
|
+
languages.forEach(lang => {
|
|
11236
|
+
const item = document.createElement('button');
|
|
11237
|
+
item.className = `
|
|
11238
|
+
w-full px-4 py-2 text-left text-sm flex items-center justify-between gap-4
|
|
11239
|
+
${lang.code === currentLang ? 'bg-primary-50 dark:bg-primary-900/20 text-primary-700 dark:text-primary-300' : 'text-gray-700 dark:text-gray-300'}
|
|
11240
|
+
hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors
|
|
11241
|
+
`;
|
|
11242
|
+
item.innerHTML = `
|
|
11243
|
+
<span class="font-medium">${lang.nativeName}</span>
|
|
11244
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">${lang.name}</span>
|
|
11245
|
+
`;
|
|
11246
|
+
item.addEventListener('click', e => {
|
|
11247
|
+
e.stopPropagation();
|
|
11248
|
+
this.selectLanguage(lang.code);
|
|
11249
|
+
});
|
|
11250
|
+
this.dropdown.appendChild(item);
|
|
11251
|
+
});
|
|
11252
|
+
this.element.appendChild(this.dropdown);
|
|
11253
|
+
// Close dropdown on outside click
|
|
11254
|
+
document.addEventListener('click', this.handleOutsideClick);
|
|
11255
|
+
}
|
|
11256
|
+
handleOutsideClick = () => {
|
|
11257
|
+
if (this.isOpen) {
|
|
11258
|
+
this.closeDropdown();
|
|
11259
|
+
}
|
|
11260
|
+
};
|
|
11261
|
+
toggleDropdown() {
|
|
11262
|
+
this.isOpen = !this.isOpen;
|
|
11263
|
+
if (this.dropdown) {
|
|
11264
|
+
this.dropdown.classList.toggle('hidden', !this.isOpen);
|
|
11265
|
+
}
|
|
11266
|
+
}
|
|
11267
|
+
closeDropdown() {
|
|
11268
|
+
this.isOpen = false;
|
|
11269
|
+
if (this.dropdown) {
|
|
11270
|
+
this.dropdown.classList.add('hidden');
|
|
11271
|
+
}
|
|
11272
|
+
}
|
|
11273
|
+
selectLanguage(code) {
|
|
11274
|
+
i18n.setLanguage(code);
|
|
11275
|
+
this.closeDropdown();
|
|
11276
|
+
this.options.onChange?.(code);
|
|
11277
|
+
}
|
|
11278
|
+
destroy() {
|
|
11279
|
+
document.removeEventListener('click', this.handleOutsideClick);
|
|
11280
|
+
super.destroy();
|
|
11281
|
+
}
|
|
11282
|
+
}
|
|
11283
|
+
|
|
8419
11284
|
/**
|
|
8420
11285
|
* Panel Renderer Component
|
|
8421
11286
|
* Handles rendering of the main offline manager panel
|
|
@@ -8433,9 +11298,12 @@ class PanelRenderer extends BaseComponent {
|
|
|
8433
11298
|
actionButtonsContainer;
|
|
8434
11299
|
regionsList;
|
|
8435
11300
|
downloadProgressContainer;
|
|
11301
|
+
languageSelector;
|
|
8436
11302
|
// Debounce mechanism
|
|
8437
11303
|
refreshTimeout;
|
|
8438
11304
|
isRefreshing = false;
|
|
11305
|
+
// Language change unsubscribe function
|
|
11306
|
+
unsubscribeLanguage;
|
|
8439
11307
|
constructor(options) {
|
|
8440
11308
|
super({});
|
|
8441
11309
|
this.offlineManager = options.offlineManager;
|
|
@@ -8443,6 +11311,10 @@ class PanelRenderer extends BaseComponent {
|
|
|
8443
11311
|
this.modalManager = options.modalManager;
|
|
8444
11312
|
this.options = options;
|
|
8445
11313
|
this.map = options.map;
|
|
11314
|
+
// Subscribe to language changes
|
|
11315
|
+
this.unsubscribeLanguage = i18n.subscribe(() => {
|
|
11316
|
+
this.refresh();
|
|
11317
|
+
});
|
|
8446
11318
|
}
|
|
8447
11319
|
createElement() {
|
|
8448
11320
|
const container = document.createElement('div');
|
|
@@ -8487,31 +11359,42 @@ class PanelRenderer extends BaseComponent {
|
|
|
8487
11359
|
this.headerContainer = document.createElement('div');
|
|
8488
11360
|
this.headerContainer.className =
|
|
8489
11361
|
'flex items-center justify-between p-6 border-b border-gray-200 dark:border-gray-700';
|
|
11362
|
+
// Apply RTL direction if needed
|
|
11363
|
+
if (i18n.isRTL()) {
|
|
11364
|
+
this.headerContainer.setAttribute('dir', 'rtl');
|
|
11365
|
+
}
|
|
8490
11366
|
// Title section
|
|
8491
11367
|
const titleSection = document.createElement('div');
|
|
8492
11368
|
titleSection.innerHTML = `
|
|
8493
|
-
<h2 class="text-xl font-semibold text-gray-900 dark:text-white"
|
|
11369
|
+
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">${t('header.title')}</h2>
|
|
8494
11370
|
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
|
8495
|
-
${regions.length
|
|
11371
|
+
${t('header.subtitle', { count: regions.length, size: formatBytes(analytics.totalStorageSize) })}
|
|
8496
11372
|
</p>
|
|
8497
11373
|
`;
|
|
8498
11374
|
// Actions section
|
|
8499
11375
|
const actionsSection = document.createElement('div');
|
|
8500
11376
|
actionsSection.className = 'flex items-center gap-2';
|
|
11377
|
+
// Language selector
|
|
11378
|
+
this.languageSelector = new LanguageSelector({
|
|
11379
|
+
onChange: () => {
|
|
11380
|
+
// Language change is handled by subscription in constructor
|
|
11381
|
+
},
|
|
11382
|
+
});
|
|
8501
11383
|
// Theme toggle button
|
|
8502
11384
|
const themeButton = new Button({
|
|
8503
11385
|
className: 'p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full',
|
|
8504
11386
|
icon: this.getThemeIcon(),
|
|
8505
|
-
title: '
|
|
11387
|
+
title: t('theme.toggle'),
|
|
8506
11388
|
onClick: () => this.handleThemeToggle(),
|
|
8507
11389
|
});
|
|
8508
11390
|
// Close button
|
|
8509
11391
|
const closeButton = new Button({
|
|
8510
11392
|
className: 'p-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full',
|
|
8511
11393
|
icon: icons.x({ size: 16, color: 'currentColor' }),
|
|
8512
|
-
title: '
|
|
11394
|
+
title: t('app.close'),
|
|
8513
11395
|
onClick: this.options.onClose,
|
|
8514
11396
|
});
|
|
11397
|
+
actionsSection.appendChild(this.languageSelector.getElement());
|
|
8515
11398
|
actionsSection.appendChild(themeButton.getElement());
|
|
8516
11399
|
actionsSection.appendChild(closeButton.getElement());
|
|
8517
11400
|
this.headerContainer.appendChild(titleSection);
|
|
@@ -8528,16 +11411,20 @@ class PanelRenderer extends BaseComponent {
|
|
|
8528
11411
|
this.actionButtonsContainer = document.createElement('div');
|
|
8529
11412
|
this.actionButtonsContainer.className =
|
|
8530
11413
|
'flex gap-3 px-6 py-4 border-b border-gray-200 dark:border-gray-700';
|
|
11414
|
+
// Apply RTL direction if needed
|
|
11415
|
+
if (i18n.isRTL()) {
|
|
11416
|
+
this.actionButtonsContainer.setAttribute('dir', 'rtl');
|
|
11417
|
+
}
|
|
8531
11418
|
// Add Region button
|
|
8532
11419
|
const addRegionButton = new Button({
|
|
8533
|
-
text: '
|
|
11420
|
+
text: t('actions.addRegion'),
|
|
8534
11421
|
variant: 'primary',
|
|
8535
11422
|
icon: icons.plus({ size: 16, color: 'white' }),
|
|
8536
11423
|
onClick: this.options.onAddRegion,
|
|
8537
11424
|
});
|
|
8538
11425
|
// Refresh button
|
|
8539
11426
|
const refreshButton = new Button({
|
|
8540
|
-
text: '
|
|
11427
|
+
text: t('actions.refresh'),
|
|
8541
11428
|
variant: 'secondary',
|
|
8542
11429
|
icon: icons.refresh({ size: 16, color: 'currentColor' }),
|
|
8543
11430
|
onClick: () => this.refresh(),
|
|
@@ -8609,18 +11496,18 @@ class PanelRenderer extends BaseComponent {
|
|
|
8609
11496
|
template: this.createCompleteStyleTemplate(styleWithSize, styleRegions),
|
|
8610
11497
|
actions: [
|
|
8611
11498
|
{
|
|
8612
|
-
label: '
|
|
11499
|
+
label: t('actions.loadStyle'),
|
|
8613
11500
|
action: 'load-style',
|
|
8614
11501
|
icon: icons.cloud({ size: 12, color: 'currentColor' }),
|
|
8615
11502
|
},
|
|
8616
11503
|
{
|
|
8617
|
-
label: '
|
|
11504
|
+
label: t('actions.fixTiles'),
|
|
8618
11505
|
action: 'fix-compressed-tiles',
|
|
8619
11506
|
variant: 'secondary',
|
|
8620
11507
|
icon: icons.settings({ size: 12, color: 'currentColor' }),
|
|
8621
11508
|
},
|
|
8622
11509
|
{
|
|
8623
|
-
label: '
|
|
11510
|
+
label: t('actions.deleteStyle'),
|
|
8624
11511
|
action: 'delete-style',
|
|
8625
11512
|
variant: 'danger',
|
|
8626
11513
|
icon: icons.trash({ size: 12, color: 'currentColor' }),
|
|
@@ -8647,12 +11534,12 @@ class PanelRenderer extends BaseComponent {
|
|
|
8647
11534
|
template: this.createRegionItemTemplate(region, false),
|
|
8648
11535
|
actions: [
|
|
8649
11536
|
{
|
|
8650
|
-
label: '
|
|
11537
|
+
label: t('app.details'),
|
|
8651
11538
|
action: 'show-details',
|
|
8652
11539
|
icon: icons.infoCircle({ size: 12, color: 'currentColor' }),
|
|
8653
11540
|
},
|
|
8654
11541
|
{
|
|
8655
|
-
label: '
|
|
11542
|
+
label: t('app.focus'),
|
|
8656
11543
|
action: 'focus-region',
|
|
8657
11544
|
icon: icons.focus({ size: 12, color: 'currentColor' }),
|
|
8658
11545
|
},
|
|
@@ -8662,7 +11549,7 @@ class PanelRenderer extends BaseComponent {
|
|
|
8662
11549
|
// icon: icons.deviceFloppy({ size: 12, color: 'currentColor' })
|
|
8663
11550
|
// },
|
|
8664
11551
|
{
|
|
8665
|
-
label: '
|
|
11552
|
+
label: t('app.delete'),
|
|
8666
11553
|
action: 'delete-region',
|
|
8667
11554
|
variant: 'danger',
|
|
8668
11555
|
icon: icons.trash({ size: 12, color: 'currentColor' }),
|
|
@@ -8675,7 +11562,7 @@ class PanelRenderer extends BaseComponent {
|
|
|
8675
11562
|
this.regionsList = new List({
|
|
8676
11563
|
className: 'flex-1 px-6 py-4 overflow-y-auto',
|
|
8677
11564
|
items: listItems,
|
|
8678
|
-
emptyText: '
|
|
11565
|
+
emptyText: t('regionList.empty'),
|
|
8679
11566
|
onItemAction: this.handleItemAction.bind(this),
|
|
8680
11567
|
onItemClick: (itemId, item) => {
|
|
8681
11568
|
const itemObj = item;
|
|
@@ -8713,12 +11600,12 @@ class PanelRenderer extends BaseComponent {
|
|
|
8713
11600
|
${region.name}
|
|
8714
11601
|
</h4>
|
|
8715
11602
|
<div class="text-xs text-slate-500 dark:text-slate-400 mt-1">
|
|
8716
|
-
${region.bounds ? `<span class="font-medium">SW:</span> ${region.bounds[0][1].toFixed(4)}, ${region.bounds[0][0].toFixed(4)} <span class="mx-1">→</span> <span class="font-medium">NE:</span> ${region.bounds[1][1].toFixed(4)}, ${region.bounds[1][0].toFixed(4)}` : '
|
|
11603
|
+
${region.bounds ? `<span class="font-medium">SW:</span> ${region.bounds[0][1].toFixed(4)}, ${region.bounds[0][0].toFixed(4)} <span class="mx-1">→</span> <span class="font-medium">NE:</span> ${region.bounds[1][1].toFixed(4)}, ${region.bounds[1][0].toFixed(4)}` : t('panel.noBounds')}
|
|
8717
11604
|
</div>
|
|
8718
11605
|
<div class="flex items-center gap-4 text-xs text-slate-400 dark:text-slate-500 mt-2">
|
|
8719
|
-
<span
|
|
8720
|
-
${region.downloadedAt ? `<span
|
|
8721
|
-
${region.size ? `<span
|
|
11606
|
+
<span>${t('regionList.zoom')}: ${region.minZoom}-${region.maxZoom}</span>
|
|
11607
|
+
${region.downloadedAt ? `<span>${t('regionList.downloaded')}: ${new Date(region.downloadedAt).toLocaleDateString()}</span>` : ''}
|
|
11608
|
+
${region.size ? `<span>${t('regionList.size')}: ${formatBytes(region.size)}</span>` : ''}
|
|
8722
11609
|
</div>
|
|
8723
11610
|
</div>
|
|
8724
11611
|
</div>
|
|
@@ -8739,16 +11626,16 @@ class PanelRenderer extends BaseComponent {
|
|
|
8739
11626
|
<div class="flex items-center justify-between">
|
|
8740
11627
|
<div class="flex-1">
|
|
8741
11628
|
<h3 class="font-semibold text-slate-800 dark:text-slate-100 text-base">
|
|
8742
|
-
${style.style?.name || style.key || '
|
|
11629
|
+
${style.style?.name || style.key || t('style.unnamedStyle')}
|
|
8743
11630
|
</h3>
|
|
8744
11631
|
<p class="text-sm text-slate-600 dark:text-slate-300 mt-1">
|
|
8745
|
-
|
|
11632
|
+
${t('regionList.styleId')}: ${style.key}
|
|
8746
11633
|
</p>
|
|
8747
11634
|
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1">
|
|
8748
11635
|
${regionCount} region${regionCount === 1 ? '' : 's'} • ${formatBytes(totalSize)}
|
|
8749
11636
|
</p>
|
|
8750
11637
|
<p class="text-sm text-slate-500 dark:text-slate-400 mt-1">
|
|
8751
|
-
|
|
11638
|
+
${t('regionList.storedStyleSize')}: ${formatBytes(style.dbSize || 0)}
|
|
8752
11639
|
</p>
|
|
8753
11640
|
</div>
|
|
8754
11641
|
<div class="flex items-center gap-2">
|
|
@@ -8780,28 +11667,28 @@ class PanelRenderer extends BaseComponent {
|
|
|
8780
11667
|
${region.name}
|
|
8781
11668
|
</h4>
|
|
8782
11669
|
<div class="text-xs text-slate-500 dark:text-slate-400 mt-1">
|
|
8783
|
-
${region.bounds ? `<span class="font-medium">SW:</span> ${region.bounds[0][1].toFixed(4)}, ${region.bounds[0][0].toFixed(4)} <span class="mx-1">→</span> <span class="font-medium">NE:</span> ${region.bounds[1][1].toFixed(4)}, ${region.bounds[1][0].toFixed(4)}` : '
|
|
11670
|
+
${region.bounds ? `<span class="font-medium">SW:</span> ${region.bounds[0][1].toFixed(4)}, ${region.bounds[0][0].toFixed(4)} <span class="mx-1">→</span> <span class="font-medium">NE:</span> ${region.bounds[1][1].toFixed(4)}, ${region.bounds[1][0].toFixed(4)}` : t('panel.noBounds')}
|
|
8784
11671
|
</div>
|
|
8785
11672
|
<div class="flex items-center gap-4 text-xs text-slate-400 dark:text-slate-500 mt-2">
|
|
8786
|
-
<span
|
|
8787
|
-
${region.downloadedAt ? `<span
|
|
8788
|
-
${region.size ? `<span
|
|
11673
|
+
<span>${t('regionList.zoom')}: ${region.minZoom}-${region.maxZoom}</span>
|
|
11674
|
+
${region.downloadedAt ? `<span>${t('regionList.downloaded')}: ${new Date(region.downloadedAt).toLocaleDateString()}</span>` : ''}
|
|
11675
|
+
${region.size ? `<span>${t('regionList.size')}: ${formatBytes(region.size)}</span>` : ''}
|
|
8789
11676
|
</div>
|
|
8790
11677
|
</div>
|
|
8791
11678
|
<div class="flex items-center gap-1 ml-2">
|
|
8792
|
-
<button class="region-action-btn p-1.5 rounded-md hover:bg-indigo-100 dark:hover:bg-indigo-900/50 text-indigo-600 dark:text-indigo-400 transition-colors duration-150" data-action="show-details" data-region-id="${region.id}" title="
|
|
11679
|
+
<button class="region-action-btn p-1.5 rounded-md hover:bg-indigo-100 dark:hover:bg-indigo-900/50 text-indigo-600 dark:text-indigo-400 transition-colors duration-150" data-action="show-details" data-region-id="${region.id}" title="${t('app.details')}">
|
|
8793
11680
|
${icons.infoCircle({ size: 14, color: 'currentColor' })}
|
|
8794
11681
|
</button>
|
|
8795
|
-
<button class="region-action-btn p-1.5 rounded-md hover:bg-emerald-100 dark:hover:bg-emerald-900/50 text-emerald-600 dark:text-emerald-400 transition-colors duration-150" data-action="focus-region" data-region-id="${region.id}" title="
|
|
11682
|
+
<button class="region-action-btn p-1.5 rounded-md hover:bg-emerald-100 dark:hover:bg-emerald-900/50 text-emerald-600 dark:text-emerald-400 transition-colors duration-150" data-action="focus-region" data-region-id="${region.id}" title="${t('app.focus')}">
|
|
8796
11683
|
${icons.focus({ size: 14, color: 'currentColor' })}
|
|
8797
11684
|
</button>
|
|
8798
|
-
<button class="region-action-btn p-1.5 rounded-md hover:bg-blue-100 dark:hover:bg-blue-900/50 text-blue-600 dark:text-blue-400 transition-colors duration-150" data-action="redownload-region" data-region-id="${region.id}" title="
|
|
11685
|
+
<button class="region-action-btn p-1.5 rounded-md hover:bg-blue-100 dark:hover:bg-blue-900/50 text-blue-600 dark:text-blue-400 transition-colors duration-150" data-action="redownload-region" data-region-id="${region.id}" title="${t('actions.redownload')}">
|
|
8799
11686
|
${icons.download({ size: 14, color: 'currentColor' })}
|
|
8800
11687
|
</button>
|
|
8801
|
-
<!-- <button class="region-action-btn p-1.5 rounded-md hover:bg-purple-100 dark:hover:bg-purple-900/50 text-purple-600 dark:text-purple-400 transition-colors duration-150" data-action="import-export" data-region-id="${region.id}" title="
|
|
11688
|
+
<!-- <button class="region-action-btn p-1.5 rounded-md hover:bg-purple-100 dark:hover:bg-purple-900/50 text-purple-600 dark:text-purple-400 transition-colors duration-150" data-action="import-export" data-region-id="${region.id}" title="${t('actions.importExport')}">
|
|
8802
11689
|
${icons.deviceFloppy({ size: 14, color: 'currentColor' })}
|
|
8803
11690
|
</button> -->
|
|
8804
|
-
<button class="region-action-btn p-1.5 rounded-md hover:bg-red-100 dark:hover:bg-red-900/50 text-red-600 dark:text-red-400 transition-colors duration-150" data-action="delete-region" data-region-id="${region.id}" title="
|
|
11691
|
+
<button class="region-action-btn p-1.5 rounded-md hover:bg-red-100 dark:hover:bg-red-900/50 text-red-600 dark:text-red-400 transition-colors duration-150" data-action="delete-region" data-region-id="${region.id}" title="${t('app.delete')}">
|
|
8805
11692
|
${icons.trash({ size: 14, color: 'currentColor' })}
|
|
8806
11693
|
</button>
|
|
8807
11694
|
</div>
|
|
@@ -8822,10 +11709,10 @@ class PanelRenderer extends BaseComponent {
|
|
|
8822
11709
|
<div class="flex items-center justify-between">
|
|
8823
11710
|
<div class="flex-1">
|
|
8824
11711
|
<h3 class="font-semibold text-blue-900 dark:text-blue-100 text-base">
|
|
8825
|
-
${style.style?.name || style.key || '
|
|
11712
|
+
${style.style?.name || style.key || t('style.unnamedStyle')}
|
|
8826
11713
|
</h3>
|
|
8827
11714
|
<p class="text-sm text-blue-700 dark:text-blue-300 mt-1">
|
|
8828
|
-
|
|
11715
|
+
${t('regionList.styleId')}: ${style.key}
|
|
8829
11716
|
</p>
|
|
8830
11717
|
<p class="text-sm text-blue-600 dark:text-blue-400 mt-1">
|
|
8831
11718
|
${regionCount} region${regionCount === 1 ? '' : 's'} • ${formatBytes(totalSize)}
|
|
@@ -8855,10 +11742,10 @@ class PanelRenderer extends BaseComponent {
|
|
|
8855
11742
|
<div class="flex items-center justify-between">
|
|
8856
11743
|
<div>
|
|
8857
11744
|
<h3 class="font-semibold text-amber-800 dark:text-amber-200 text-sm">
|
|
8858
|
-
|
|
11745
|
+
${t('regionList.orphanedHeader')}
|
|
8859
11746
|
</h3>
|
|
8860
11747
|
<p class="text-xs text-amber-700 dark:text-amber-300 mt-1">
|
|
8861
|
-
${
|
|
11748
|
+
${t('regionList.orphanedDescription', { count: regions.length })}
|
|
8862
11749
|
</p>
|
|
8863
11750
|
</div>
|
|
8864
11751
|
<div class="text-amber-500 dark:text-amber-400">
|
|
@@ -8876,7 +11763,7 @@ class PanelRenderer extends BaseComponent {
|
|
|
8876
11763
|
return `
|
|
8877
11764
|
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
|
|
8878
11765
|
<h3 class="text-sm font-medium text-blue-900 dark:text-blue-100 mb-3">
|
|
8879
|
-
|
|
11766
|
+
${t('download.activeCount', { count: downloadArray.length })}
|
|
8880
11767
|
</h3>
|
|
8881
11768
|
<div class="space-y-2">
|
|
8882
11769
|
${downloadArray
|
|
@@ -8956,10 +11843,10 @@ class PanelRenderer extends BaseComponent {
|
|
|
8956
11843
|
if (!region)
|
|
8957
11844
|
return;
|
|
8958
11845
|
const confirmModal = new ConfirmationModal({
|
|
8959
|
-
title: '
|
|
8960
|
-
message:
|
|
8961
|
-
confirmText: '
|
|
8962
|
-
cancelText: '
|
|
11846
|
+
title: t('delete.regionTitle'),
|
|
11847
|
+
message: t('delete.regionMessage', { name: region.name }),
|
|
11848
|
+
confirmText: t('app.delete'),
|
|
11849
|
+
cancelText: t('app.cancel'),
|
|
8963
11850
|
onConfirm: async () => {
|
|
8964
11851
|
try {
|
|
8965
11852
|
await this.offlineManager.deleteRegion(regionId);
|
|
@@ -8995,10 +11882,10 @@ class PanelRenderer extends BaseComponent {
|
|
|
8995
11882
|
return;
|
|
8996
11883
|
}
|
|
8997
11884
|
const confirmModal = new ConfirmationModal({
|
|
8998
|
-
title: '
|
|
8999
|
-
message:
|
|
9000
|
-
confirmText: '
|
|
9001
|
-
cancelText: '
|
|
11885
|
+
title: t('redownload.title'),
|
|
11886
|
+
message: t('redownload.message', { name: region.name }),
|
|
11887
|
+
confirmText: t('redownload.button'),
|
|
11888
|
+
cancelText: t('app.cancel'),
|
|
9002
11889
|
onConfirm: async () => {
|
|
9003
11890
|
try {
|
|
9004
11891
|
this.modalManager.close();
|
|
@@ -9026,14 +11913,14 @@ class PanelRenderer extends BaseComponent {
|
|
|
9026
11913
|
}
|
|
9027
11914
|
else {
|
|
9028
11915
|
panelLogger.error('Download manager not available');
|
|
9029
|
-
alert('
|
|
11916
|
+
alert(t('alert.downloadManagerNotAvailable'));
|
|
9030
11917
|
}
|
|
9031
11918
|
// Refresh the panel to show the new region
|
|
9032
11919
|
await this.refresh();
|
|
9033
11920
|
}
|
|
9034
11921
|
catch (error) {
|
|
9035
11922
|
panelLogger.error('Failed to re-download region:', error);
|
|
9036
|
-
alert(
|
|
11923
|
+
alert(`${t('alert.failedToRedownload')}: ${error instanceof Error ? error.message : t('alert.unknownError')}`);
|
|
9037
11924
|
}
|
|
9038
11925
|
},
|
|
9039
11926
|
onCancel: () => {
|
|
@@ -9123,10 +12010,10 @@ class PanelRenderer extends BaseComponent {
|
|
|
9123
12010
|
*/
|
|
9124
12011
|
renderErrorState(panelElement) {
|
|
9125
12012
|
panelElement.innerHTML = `
|
|
9126
|
-
<div class="flex items-center justify-center h-full text-red-600 dark:text-red-400">
|
|
12013
|
+
<div class="flex items-center justify-center h-full text-red-600 dark:text-red-400" ${i18n.isRTL() ? 'dir="rtl"' : ''}>
|
|
9127
12014
|
<div class="text-center">
|
|
9128
|
-
<p class="text-lg font-medium"
|
|
9129
|
-
<p class="text-sm mt-2"
|
|
12015
|
+
<p class="text-lg font-medium">${t('error.loadingContent')}</p>
|
|
12016
|
+
<p class="text-sm mt-2">${t('error.tryAgain')}</p>
|
|
9130
12017
|
</div>
|
|
9131
12018
|
</div>
|
|
9132
12019
|
`;
|
|
@@ -9194,12 +12081,12 @@ class PanelRenderer extends BaseComponent {
|
|
|
9194
12081
|
template: this.createRegionItemTemplate(region, false),
|
|
9195
12082
|
actions: [
|
|
9196
12083
|
{
|
|
9197
|
-
label: '
|
|
12084
|
+
label: t('app.details'),
|
|
9198
12085
|
action: 'show-details',
|
|
9199
12086
|
icon: icons.infoCircle({ size: 12, color: 'currentColor' }),
|
|
9200
12087
|
},
|
|
9201
12088
|
{
|
|
9202
|
-
label: '
|
|
12089
|
+
label: t('app.focus'),
|
|
9203
12090
|
action: 'focus-region',
|
|
9204
12091
|
icon: icons.focus({ size: 12, color: 'currentColor' }),
|
|
9205
12092
|
},
|
|
@@ -9209,7 +12096,7 @@ class PanelRenderer extends BaseComponent {
|
|
|
9209
12096
|
// icon: icons.deviceFloppy({ size: 12, color: 'currentColor' })
|
|
9210
12097
|
// },
|
|
9211
12098
|
{
|
|
9212
|
-
label: '
|
|
12099
|
+
label: t('app.delete'),
|
|
9213
12100
|
action: 'delete-region',
|
|
9214
12101
|
variant: 'danger',
|
|
9215
12102
|
icon: icons.trash({ size: 12, color: 'currentColor' }),
|
|
@@ -9220,7 +12107,7 @@ class PanelRenderer extends BaseComponent {
|
|
|
9220
12107
|
this.regionsList = new List({
|
|
9221
12108
|
className: 'flex-1 px-6 py-4 overflow-y-auto',
|
|
9222
12109
|
items: listItems,
|
|
9223
|
-
emptyText: '
|
|
12110
|
+
emptyText: t('regionList.emptyFallback'),
|
|
9224
12111
|
onItemAction: this.handleItemAction.bind(this),
|
|
9225
12112
|
onItemClick: (itemId, _item) => {
|
|
9226
12113
|
this.handleShowRegionDetails(itemId);
|
|
@@ -9290,6 +12177,12 @@ class PanelRenderer extends BaseComponent {
|
|
|
9290
12177
|
if (this.regionsList) {
|
|
9291
12178
|
this.regionsList.destroy();
|
|
9292
12179
|
}
|
|
12180
|
+
if (this.languageSelector) {
|
|
12181
|
+
this.languageSelector.destroy();
|
|
12182
|
+
}
|
|
12183
|
+
if (this.unsubscribeLanguage) {
|
|
12184
|
+
this.unsubscribeLanguage();
|
|
12185
|
+
}
|
|
9293
12186
|
super.destroy();
|
|
9294
12187
|
}
|
|
9295
12188
|
/**
|
|
@@ -9346,9 +12239,9 @@ class PanelRenderer extends BaseComponent {
|
|
|
9346
12239
|
const stats = await countCompressedTiles();
|
|
9347
12240
|
if (stats.gzipped === 0) {
|
|
9348
12241
|
const confirmModal = new ConfirmationModal({
|
|
9349
|
-
title: '
|
|
9350
|
-
message:
|
|
9351
|
-
confirmText: '
|
|
12242
|
+
title: t('fixTiles.noIssuesTitle'),
|
|
12243
|
+
message: t('fixTiles.noIssuesMessage'),
|
|
12244
|
+
confirmText: t('app.ok'),
|
|
9352
12245
|
cancelText: '',
|
|
9353
12246
|
onConfirm: () => {
|
|
9354
12247
|
this.modalManager.close();
|
|
@@ -9362,10 +12255,10 @@ class PanelRenderer extends BaseComponent {
|
|
|
9362
12255
|
return;
|
|
9363
12256
|
}
|
|
9364
12257
|
const confirmModal = new ConfirmationModal({
|
|
9365
|
-
title: '
|
|
9366
|
-
message:
|
|
9367
|
-
confirmText: '
|
|
9368
|
-
cancelText: '
|
|
12258
|
+
title: t('fixTiles.title'),
|
|
12259
|
+
message: t('fixTiles.message', { count: stats.gzipped }),
|
|
12260
|
+
confirmText: t('fixTiles.button'),
|
|
12261
|
+
cancelText: t('app.cancel'),
|
|
9369
12262
|
onConfirm: async () => {
|
|
9370
12263
|
try {
|
|
9371
12264
|
panelLogger.debug('Cleaning up compressed tiles...');
|
|
@@ -9374,9 +12267,9 @@ class PanelRenderer extends BaseComponent {
|
|
|
9374
12267
|
this.modalManager.close();
|
|
9375
12268
|
// Show success message
|
|
9376
12269
|
const successModal = new ConfirmationModal({
|
|
9377
|
-
title: '
|
|
9378
|
-
message:
|
|
9379
|
-
confirmText: '
|
|
12270
|
+
title: t('fixTiles.completeTitle'),
|
|
12271
|
+
message: t('fixTiles.completeMessage', { count: result.removed }),
|
|
12272
|
+
confirmText: t('app.ok'),
|
|
9380
12273
|
cancelText: '',
|
|
9381
12274
|
onConfirm: () => {
|
|
9382
12275
|
this.modalManager.close();
|
|
@@ -9412,19 +12305,19 @@ class PanelRenderer extends BaseComponent {
|
|
|
9412
12305
|
async handleLoadStyle(styleData) {
|
|
9413
12306
|
if (!this.map) {
|
|
9414
12307
|
panelLogger.warn('Map not available for loading style');
|
|
9415
|
-
alert('
|
|
12308
|
+
alert(t('error.mapNotInitialized'));
|
|
9416
12309
|
return;
|
|
9417
12310
|
}
|
|
9418
12311
|
try {
|
|
9419
12312
|
const styleEntry = styleData;
|
|
9420
12313
|
if (!styleEntry || !styleEntry.style) {
|
|
9421
12314
|
panelLogger.error('Invalid style data - missing style property');
|
|
9422
|
-
alert('
|
|
12315
|
+
alert(t('error.invalidStyleData'));
|
|
9423
12316
|
return;
|
|
9424
12317
|
}
|
|
9425
12318
|
if (!styleEntry.key) {
|
|
9426
12319
|
panelLogger.error('Invalid style data - missing key property');
|
|
9427
|
-
alert('
|
|
12320
|
+
alert(t('error.invalidStyleData'));
|
|
9428
12321
|
return;
|
|
9429
12322
|
}
|
|
9430
12323
|
panelLogger.debug('Loading style from IndexedDB:', {
|
|
@@ -9440,12 +12333,12 @@ class PanelRenderer extends BaseComponent {
|
|
|
9440
12333
|
// Validate style structure
|
|
9441
12334
|
if (!styleEntry.style.sources || Object.keys(styleEntry.style.sources).length === 0) {
|
|
9442
12335
|
panelLogger.error('Style is missing sources');
|
|
9443
|
-
alert('
|
|
12336
|
+
alert(t('error.styleMissingSources'));
|
|
9444
12337
|
return;
|
|
9445
12338
|
}
|
|
9446
12339
|
if (!styleEntry.style.layers || styleEntry.style.layers.length === 0) {
|
|
9447
12340
|
panelLogger.error('Style is missing layers');
|
|
9448
|
-
alert('
|
|
12341
|
+
alert(t('error.styleMissingLayers'));
|
|
9449
12342
|
return;
|
|
9450
12343
|
}
|
|
9451
12344
|
// Check for compressed tiles
|
|
@@ -9454,9 +12347,9 @@ class PanelRenderer extends BaseComponent {
|
|
|
9454
12347
|
const compressed = await countCompressedTiles();
|
|
9455
12348
|
if (compressed.gzipped > 0) {
|
|
9456
12349
|
panelLogger.warn(`Found ${compressed.gzipped} compressed tiles that may cause rendering issues`);
|
|
9457
|
-
const shouldContinue = confirm(
|
|
9458
|
-
|
|
9459
|
-
`
|
|
12350
|
+
const shouldContinue = confirm(`${t('warning.compressedTiles', { count: compressed.gzipped })}\n\n` +
|
|
12351
|
+
`${t('warning.compressedTilesRecommendation')}\n\n` +
|
|
12352
|
+
`${t('warning.continueAnyway')}`);
|
|
9460
12353
|
if (!shouldContinue) {
|
|
9461
12354
|
return;
|
|
9462
12355
|
}
|
|
@@ -9525,7 +12418,7 @@ class PanelRenderer extends BaseComponent {
|
|
|
9525
12418
|
}
|
|
9526
12419
|
catch (setStyleError) {
|
|
9527
12420
|
panelLogger.error('Error calling setStyle:', setStyleError);
|
|
9528
|
-
alert(
|
|
12421
|
+
alert(`${t('alert.failedToApplyStyle')}: ${setStyleError instanceof Error ? setStyleError.message : t('alert.unknownError')}`);
|
|
9529
12422
|
return;
|
|
9530
12423
|
}
|
|
9531
12424
|
}
|
|
@@ -9535,7 +12428,7 @@ class PanelRenderer extends BaseComponent {
|
|
|
9535
12428
|
message: error instanceof Error ? error.message : String(error),
|
|
9536
12429
|
stack: error instanceof Error ? error.stack : undefined,
|
|
9537
12430
|
});
|
|
9538
|
-
alert(
|
|
12431
|
+
alert(`${t('alert.failedToLoadStyle')}: ${error instanceof Error ? error.message : t('alert.unknownError')}`);
|
|
9539
12432
|
}
|
|
9540
12433
|
}
|
|
9541
12434
|
/**
|
|
@@ -9545,10 +12438,10 @@ class PanelRenderer extends BaseComponent {
|
|
|
9545
12438
|
try {
|
|
9546
12439
|
const style = styleData;
|
|
9547
12440
|
const confirmModal = new ConfirmationModal({
|
|
9548
|
-
title: '
|
|
9549
|
-
message:
|
|
9550
|
-
confirmText: '
|
|
9551
|
-
cancelText: '
|
|
12441
|
+
title: t('delete.styleTitle'),
|
|
12442
|
+
message: t('delete.styleMessage', { name: style.style?.name || style.key }),
|
|
12443
|
+
confirmText: t('actions.deleteStyle'),
|
|
12444
|
+
cancelText: t('app.cancel'),
|
|
9552
12445
|
onConfirm: async () => {
|
|
9553
12446
|
try {
|
|
9554
12447
|
const { deleteStyleById } = await Promise.resolve().then(function () { return styleService; });
|
|
@@ -14628,8 +17521,8 @@ class RegionFormModal {
|
|
|
14628
17521
|
show() {
|
|
14629
17522
|
const { bounds, area } = this.options;
|
|
14630
17523
|
const modalConfig = {
|
|
14631
|
-
title: '
|
|
14632
|
-
subtitle:
|
|
17524
|
+
title: t('regionForm.title'),
|
|
17525
|
+
subtitle: t('regionForm.subtitle', { area: area.toString() }),
|
|
14633
17526
|
size: 'md',
|
|
14634
17527
|
closable: true,
|
|
14635
17528
|
showThemeToggle: true,
|
|
@@ -14654,19 +17547,22 @@ class RegionFormModal {
|
|
|
14654
17547
|
const styleUrl = this.options.styleUrl;
|
|
14655
17548
|
const form = document.createElement('div');
|
|
14656
17549
|
form.className = 'flex flex-col gap-6 py-2';
|
|
17550
|
+
if (i18n.isRTL()) {
|
|
17551
|
+
form.setAttribute('dir', 'rtl');
|
|
17552
|
+
}
|
|
14657
17553
|
// Region info display (Moved to top for better context)
|
|
14658
17554
|
const infoGroup = document.createElement('div');
|
|
14659
17555
|
infoGroup.className =
|
|
14660
17556
|
'grid grid-cols-2 gap-4 p-4 rounded-xl glass-input border border-gray-100/50 dark:border-gray-700/50 bg-gray-50/50 dark:bg-gray-800/50';
|
|
14661
17557
|
infoGroup.innerHTML = `
|
|
14662
17558
|
<div class="flex flex-col justify-center">
|
|
14663
|
-
<strong class="text-xs font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1"
|
|
17559
|
+
<strong class="text-xs font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">${t('regionForm.area')}</strong>
|
|
14664
17560
|
<div class="text-xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-gray-900 to-gray-700 dark:from-white dark:to-gray-300">
|
|
14665
17561
|
${area.toLocaleString()} <span class="text-sm font-medium text-gray-500">km²</span>
|
|
14666
17562
|
</div>
|
|
14667
17563
|
</div>
|
|
14668
17564
|
<div>
|
|
14669
|
-
<strong class="text-xs font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1"
|
|
17565
|
+
<strong class="text-xs font-bold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-1">${t('regionForm.bounds')}</strong>
|
|
14670
17566
|
<div class="text-xs font-mono text-gray-600 dark:text-gray-300 leading-relaxed bg-white/50 dark:bg-black/20 p-2 rounded-lg">
|
|
14671
17567
|
${west.toFixed(4)}, ${south.toFixed(4)}<br>
|
|
14672
17568
|
${east.toFixed(4)}, ${north.toFixed(4)}
|
|
@@ -14678,12 +17574,12 @@ class RegionFormModal {
|
|
|
14678
17574
|
const nameGroup = document.createElement('div');
|
|
14679
17575
|
nameGroup.innerHTML = `
|
|
14680
17576
|
<label class="block mb-2 text-sm font-semibold text-gray-700 dark:text-gray-300">
|
|
14681
|
-
|
|
17577
|
+
${t('regionForm.name')}
|
|
14682
17578
|
</label>
|
|
14683
17579
|
`;
|
|
14684
17580
|
this.nameInput = document.createElement('input');
|
|
14685
17581
|
this.nameInput.type = 'text';
|
|
14686
|
-
this.nameInput.placeholder = '
|
|
17582
|
+
this.nameInput.placeholder = t('regionForm.namePlaceholder');
|
|
14687
17583
|
this.nameInput.className =
|
|
14688
17584
|
'w-full px-4 py-3 rounded-xl text-base glass-input text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-primary-500/50 transition-all shadow-sm';
|
|
14689
17585
|
nameGroup.appendChild(this.nameInput);
|
|
@@ -14694,7 +17590,7 @@ class RegionFormModal {
|
|
|
14694
17590
|
const minZoomDiv = document.createElement('div');
|
|
14695
17591
|
minZoomDiv.innerHTML = `
|
|
14696
17592
|
<label class="block mb-2 text-sm font-semibold text-gray-700 dark:text-gray-300">
|
|
14697
|
-
|
|
17593
|
+
${t('regionForm.minZoom')}
|
|
14698
17594
|
</label>
|
|
14699
17595
|
`;
|
|
14700
17596
|
this.minZoomInput = document.createElement('input');
|
|
@@ -14708,7 +17604,7 @@ class RegionFormModal {
|
|
|
14708
17604
|
const maxZoomDiv = document.createElement('div');
|
|
14709
17605
|
maxZoomDiv.innerHTML = `
|
|
14710
17606
|
<label class="block mb-2 text-sm font-semibold text-gray-700 dark:text-gray-300">
|
|
14711
|
-
|
|
17607
|
+
${t('regionForm.maxZoom')}
|
|
14712
17608
|
</label>
|
|
14713
17609
|
`;
|
|
14714
17610
|
this.maxZoomInput = document.createElement('input');
|
|
@@ -14726,7 +17622,7 @@ class RegionFormModal {
|
|
|
14726
17622
|
const styleGroup = document.createElement('div');
|
|
14727
17623
|
styleGroup.innerHTML = `
|
|
14728
17624
|
<label class="block mb-2 text-sm font-semibold text-gray-700 dark:text-gray-300">
|
|
14729
|
-
|
|
17625
|
+
${t('regionForm.styleUrl')}
|
|
14730
17626
|
</label>
|
|
14731
17627
|
`;
|
|
14732
17628
|
this.styleUrlInput = document.createElement('input');
|
|
@@ -14741,16 +17637,16 @@ class RegionFormModal {
|
|
|
14741
17637
|
const providerGroup = document.createElement('div');
|
|
14742
17638
|
providerGroup.innerHTML = `
|
|
14743
17639
|
<label class="block mb-2 text-sm font-semibold text-gray-700 dark:text-gray-300">
|
|
14744
|
-
|
|
17640
|
+
${t('regionForm.provider')}
|
|
14745
17641
|
</label>
|
|
14746
17642
|
`;
|
|
14747
17643
|
this.providerSelect = document.createElement('select');
|
|
14748
17644
|
this.providerSelect.className =
|
|
14749
17645
|
'w-full px-4 py-3 rounded-xl text-base glass-input text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-primary-500/50 transition-all appearance-none cursor-pointer shadow-sm';
|
|
14750
17646
|
this.providerSelect.innerHTML = `
|
|
14751
|
-
<option value="auto"
|
|
14752
|
-
<option value="mapbox"
|
|
14753
|
-
<option value="maplibre"
|
|
17647
|
+
<option value="auto">${t('regionForm.providerAuto')}</option>
|
|
17648
|
+
<option value="mapbox">${t('regionForm.providerMapbox')}</option>
|
|
17649
|
+
<option value="maplibre">${t('regionForm.providerMaplibre')}</option>
|
|
14754
17650
|
`;
|
|
14755
17651
|
this.providerSelect.addEventListener('change', () => this.handleProviderChange());
|
|
14756
17652
|
providerGroup.appendChild(this.providerSelect);
|
|
@@ -14758,7 +17654,7 @@ class RegionFormModal {
|
|
|
14758
17654
|
const providerInfo = document.createElement('div');
|
|
14759
17655
|
providerInfo.className =
|
|
14760
17656
|
'text-xs text-gray-500 dark:text-gray-400 mt-2 ml-1 flex items-center gap-1';
|
|
14761
|
-
providerInfo.innerHTML = `${icons.infoCircle({ size: 12 })}
|
|
17657
|
+
providerInfo.innerHTML = `${icons.infoCircle({ size: 12 })} ${t('regionForm.providerInfo')}`;
|
|
14762
17658
|
providerGroup.appendChild(providerInfo);
|
|
14763
17659
|
form.appendChild(providerGroup);
|
|
14764
17660
|
// Access Token input
|
|
@@ -14766,8 +17662,8 @@ class RegionFormModal {
|
|
|
14766
17662
|
this.accessTokenGroup.className = 'hidden';
|
|
14767
17663
|
this.accessTokenGroup.innerHTML = `
|
|
14768
17664
|
<label class="block mb-2 text-sm font-semibold text-gray-700 dark:text-gray-300">
|
|
14769
|
-
|
|
14770
|
-
<span class="ml-2 text-xs font-normal text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 rounded-full border border-amber-200 dark:border-amber-800"
|
|
17665
|
+
${t('regionForm.accessToken')}
|
|
17666
|
+
<span class="ml-2 text-xs font-normal text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 rounded-full border border-amber-200 dark:border-amber-800">${t('regionForm.accessTokenRequired')}</span>
|
|
14771
17667
|
</label>
|
|
14772
17668
|
`;
|
|
14773
17669
|
this.accessTokenInput = document.createElement('input');
|
|
@@ -14780,7 +17676,7 @@ class RegionFormModal {
|
|
|
14780
17676
|
const tokenHelp = document.createElement('div');
|
|
14781
17677
|
tokenHelp.className = 'text-xs text-gray-500 dark:text-gray-400 mt-2 ml-1';
|
|
14782
17678
|
tokenHelp.innerHTML = `
|
|
14783
|
-
|
|
17679
|
+
${t('regionForm.accessTokenHelp')} <a href="https://account.mapbox.com/access-tokens/" target="_blank" rel="noopener noreferrer" class="text-primary-600 dark:text-primary-400 hover:underline font-medium">Mapbox Account</a>
|
|
14784
17680
|
`;
|
|
14785
17681
|
this.accessTokenGroup.appendChild(tokenHelp);
|
|
14786
17682
|
form.appendChild(this.accessTokenGroup);
|
|
@@ -14844,9 +17740,12 @@ class RegionFormModal {
|
|
|
14844
17740
|
createFooter() {
|
|
14845
17741
|
const footer = document.createElement('div');
|
|
14846
17742
|
footer.className = 'flex gap-2 justify-end';
|
|
17743
|
+
if (i18n.isRTL()) {
|
|
17744
|
+
footer.setAttribute('dir', 'rtl');
|
|
17745
|
+
}
|
|
14847
17746
|
// Cancel button
|
|
14848
17747
|
const cancelButton = new Button({
|
|
14849
|
-
text: '
|
|
17748
|
+
text: t('app.cancel'),
|
|
14850
17749
|
variant: 'secondary',
|
|
14851
17750
|
onClick: () => {
|
|
14852
17751
|
this.modal?.hide();
|
|
@@ -14855,7 +17754,7 @@ class RegionFormModal {
|
|
|
14855
17754
|
});
|
|
14856
17755
|
// Save button
|
|
14857
17756
|
const saveButton = new Button({
|
|
14858
|
-
text: '
|
|
17757
|
+
text: t('regionForm.downloadRegion'),
|
|
14859
17758
|
variant: 'primary',
|
|
14860
17759
|
icon: icons.download({ size: 16, color: 'white' }),
|
|
14861
17760
|
onClick: () => this.handleSave(),
|
|
@@ -15836,6 +18735,15 @@ class OfflineManagerControl {
|
|
|
15836
18735
|
if (this.regionControl?.isSelectionActive()) {
|
|
15837
18736
|
this.regionControl.cancelSelection();
|
|
15838
18737
|
}
|
|
18738
|
+
// Apply RTL direction if needed
|
|
18739
|
+
if (i18n.isRTL()) {
|
|
18740
|
+
this.panel.setAttribute('dir', 'rtl');
|
|
18741
|
+
this.panel.classList.add('rtl');
|
|
18742
|
+
}
|
|
18743
|
+
else {
|
|
18744
|
+
this.panel.setAttribute('dir', 'ltr');
|
|
18745
|
+
this.panel.classList.remove('rtl');
|
|
18746
|
+
}
|
|
15839
18747
|
this.panel.classList.remove('hidden');
|
|
15840
18748
|
this.renderPanel();
|
|
15841
18749
|
}
|
|
@@ -16289,5 +19197,5 @@ class OfflineManagerControl {
|
|
|
16289
19197
|
}
|
|
16290
19198
|
}
|
|
16291
19199
|
|
|
16292
|
-
export { AnalyticsService, CONTENT_TYPES, CategorizedError, CleanupService, DB_NAME, DB_VERSION, DOWNLOAD_DEFAULTS, ERROR_MESSAGES, ErrorType, FontService, GLYPH_CONFIG, GZIP_MAGIC_BYTES, GlyphService, ImportExportService, LogLevel, MAP_PROVIDERS, MaintenanceService, OfflineManagerControl, OfflineMapManager, RESOURCE_TYPES, RegionService, ResourceService, STORAGE_CONFIG, STORE_NAMES, STYLE_CONFIG, SUCCESS_MESSAGES, ScopedLogger, SpriteService, TILE_CONFIG, TileService, URL_SCHEMES, VALIDATION_PATTERNS, aggregateErrors, applyProxy, calculateBBoxArea, categorizeError, cleanupCompressedTiles, cleanupOldFonts, cleanupOldGlyphs, cleanupOldSprites, cleanupOldStyles, cleanupOldTiles, cleanupService, clearAllCaches, clearRegionStyleCache, configureLogger, configureProxy, countCompressedTiles, createProgressTracker, createTileKey, dbPromise, OfflineMapManager as default, deleteStyleById, deleteStyles, deriveTileExtension, detectStyleProvider, downloadFonts, downloadGlyphs, downloadSprites, downloadStyleWithProvider, downloadStyles, downloadTiles, estimateTileCount, extractAccessToken, extractTileKey, fetchResource, fetchResourceWithRetry, fetchWithRetry, fontService, formatBytes, formatDate, formatDateTime, formatDuration, formatNumber, formatPercentage, generateGlyphUrlsFromStyle, getDefaultStyleConfig, getFontAnalytics, getFontStats, getGlyphAnalytics, getGlyphStats, getIcon, getProxyConfig, getRegionAnalytics, getSpriteAnalytics, getSpriteStats, getStyleStats, getTileAnalytics, getTileStats, getUserErrorMessage, glyphService, icons, idbFetchHandler, isCorsError, isRetryableError, isStyleDownloaded, listTileKeysInIDB, loadGlyphs, loadStyleById, loadStyles, logError, logger, normalizeStyleUrl, optimizeStorage, parseTileKey, patchStyleForOffline, performCleanup, processBatch, processStyleSources, requiresAuthentication, resetProxyConfig, safeExecute, safeValidate, setupAutoCleanup, shouldProxy, spriteService, stopAutoCleanup, tileService, validateBatchSize, validateBounds, validateRegion, validateRegionId, validateRegionOptions, validateResource, validateStyleForProvider, validateStyleUrl, validateTimeout, validateZoomLevels, verifyAndRepairFonts, verifyAndRepairGlyphs, verifyAndRepairSprites };
|
|
19200
|
+
export { AnalyticsService, CONTENT_TYPES, CategorizedError, CleanupService, DB_NAME, DB_VERSION, DOWNLOAD_DEFAULTS, ERROR_MESSAGES, ErrorType, FontService, GLYPH_CONFIG, GZIP_MAGIC_BYTES, GlyphService, ImportExportService, LogLevel, MAP_PROVIDERS, MaintenanceService, OfflineManagerControl, OfflineMapManager, RESOURCE_TYPES, RegionService, ResourceService, STORAGE_CONFIG, STORE_NAMES, STYLE_CONFIG, SUCCESS_MESSAGES, ScopedLogger, SpriteService, TILE_CONFIG, TileService, URL_SCHEMES, VALIDATION_PATTERNS, aggregateErrors, applyProxy, calculateBBoxArea, categorizeError, cleanupCompressedTiles, cleanupOldFonts, cleanupOldGlyphs, cleanupOldSprites, cleanupOldStyles, cleanupOldTiles, cleanupService, clearAllCaches, clearRegionStyleCache, configureLogger, configureProxy, countCompressedTiles, createProgressTracker, createTileKey, dbPromise, OfflineMapManager as default, deleteStyleById, deleteStyles, deriveTileExtension, detectStyleProvider, downloadFonts, downloadGlyphs, downloadSprites, downloadStyleWithProvider, downloadStyles, downloadTiles, estimateTileCount, extractAccessToken, extractTileKey, fetchResource, fetchResourceWithRetry, fetchWithRetry, fontService, formatBytes, formatDate, formatDateTime, formatDuration, formatNumber, formatPercentage, generateGlyphUrlsFromStyle, getDefaultStyleConfig, getFontAnalytics, getFontStats, getGlyphAnalytics, getGlyphStats, getIcon, getProxyConfig, getRegionAnalytics, getSpriteAnalytics, getSpriteStats, getStyleStats, getTileAnalytics, getTileStats, getUserErrorMessage, glyphService, i18n, icons, idbFetchHandler, isCorsError, isRetryableError, isStyleDownloaded, listTileKeysInIDB, loadGlyphs, loadStyleById, loadStyles, logError, logger, normalizeStyleUrl, optimizeStorage, parseTileKey, patchStyleForOffline, performCleanup, processBatch, processStyleSources, requiresAuthentication, resetProxyConfig, safeExecute, safeValidate, setupAutoCleanup, shouldProxy, spriteService, stopAutoCleanup, t, tileService, validateBatchSize, validateBounds, validateRegion, validateRegionId, validateRegionOptions, validateResource, validateStyleForProvider, validateStyleUrl, validateTimeout, validateZoomLevels, verifyAndRepairFonts, verifyAndRepairGlyphs, verifyAndRepairSprites };
|
|
16293
19201
|
//# sourceMappingURL=index.esm.js.map
|