feat: add i18n framework with Indonesian translation support (#124)
* feat: add i18n support with separate translation files - Extract translations from store.js to separate files for easier management - Add translation files for English (en.js), Indonesian (id.js), Turkish (tr.js), and Chinese (zh.js) - Load translations via window.translations object before Alpine store initialization - Add Bahasa Indonesia option to language selector * feat: translate remaining hardcoded UI strings - Update index.html to use t() for Menu and GitHub labels - Update views to translate Tier, Quota, Live, tier badges, and close button - Update components to use translated error messages and confirmation dialogs - Update utils to use translated validation and error messages - Update app-init.js to use translated OAuth success/error messages
This commit is contained in:
committed by
GitHub
parent
9ffb83ab74
commit
e2d03f9b25
@@ -26,7 +26,7 @@ window.AccountActions.refreshAccount = async function(email) {
|
||||
|
||||
const data = await response.json();
|
||||
if (data.status !== 'ok') {
|
||||
return { success: false, error: data.error || 'Refresh failed' };
|
||||
return { success: false, error: data.error || Alpine.store('global').t('refreshFailed') };
|
||||
}
|
||||
|
||||
// 触发数据刷新
|
||||
@@ -73,7 +73,7 @@ window.AccountActions.toggleAccount = async function(email, enabled) {
|
||||
|
||||
const data = await response.json();
|
||||
if (data.status !== 'ok') {
|
||||
throw new Error(data.error || 'Toggle failed');
|
||||
throw new Error(data.error || Alpine.store('global').t('toggleFailed'));
|
||||
}
|
||||
|
||||
// 确认服务器状态
|
||||
@@ -111,7 +111,7 @@ window.AccountActions.deleteAccount = async function(email) {
|
||||
|
||||
const data = await response.json();
|
||||
if (data.status !== 'ok') {
|
||||
return { success: false, error: data.error || 'Delete failed' };
|
||||
return { success: false, error: data.error || Alpine.store('global').t('deleteFailed') };
|
||||
}
|
||||
|
||||
// 触发数据刷新
|
||||
@@ -146,7 +146,7 @@ window.AccountActions.getFixAccountUrl = async function(email) {
|
||||
|
||||
const data = await response.json();
|
||||
if (data.status !== 'ok') {
|
||||
return { success: false, error: data.error || 'Failed to get auth URL' };
|
||||
return { success: false, error: data.error || Alpine.store('global').t('authUrlFailed') };
|
||||
}
|
||||
|
||||
return { success: true, url: data.url };
|
||||
@@ -176,7 +176,7 @@ window.AccountActions.reloadAccounts = async function() {
|
||||
|
||||
const data = await response.json();
|
||||
if (data.status !== 'ok') {
|
||||
return { success: false, error: data.error || 'Reload failed' };
|
||||
return { success: false, error: data.error || Alpine.store('global').t('reloadFailed') };
|
||||
}
|
||||
|
||||
// 触发数据刷新
|
||||
|
||||
@@ -13,18 +13,19 @@ window.ErrorHandler = window.ErrorHandler || {};
|
||||
* @param {Function} options.onError - Custom error handler callback
|
||||
* @returns {Promise<any>} Result of the function or undefined on error
|
||||
*/
|
||||
window.ErrorHandler.safeAsync = async function(fn, errorMessage = 'Operation failed', options = {}) {
|
||||
window.ErrorHandler.safeAsync = async function(fn, errorMessage = null, options = {}) {
|
||||
const { rethrow = false, onError = null } = options;
|
||||
const store = Alpine.store('global');
|
||||
const defaultErrorMessage = errorMessage || store.t('operationFailed');
|
||||
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
// Log error for debugging
|
||||
console.error(`[ErrorHandler] ${errorMessage}:`, error);
|
||||
console.error(`[ErrorHandler] ${defaultErrorMessage}:`, error);
|
||||
|
||||
// Show toast notification
|
||||
const fullMessage = `${errorMessage}: ${error.message || 'Unknown error'}`;
|
||||
const fullMessage = `${defaultErrorMessage}: ${error.message || store.t('unknownError')}`;
|
||||
store.showToast(fullMessage, 'error');
|
||||
|
||||
// Call custom error handler if provided
|
||||
@@ -51,11 +52,11 @@ window.ErrorHandler.safeAsync = async function(fn, errorMessage = 'Operation fai
|
||||
* @param {string} errorMessage - Error message prefix
|
||||
* @returns {Function} Wrapped method
|
||||
*/
|
||||
window.ErrorHandler.wrapMethod = function(method, errorMessage = 'Operation failed') {
|
||||
window.ErrorHandler.wrapMethod = function(method, errorMessage = null) {
|
||||
return async function(...args) {
|
||||
return window.ErrorHandler.safeAsync(
|
||||
() => method.apply(this, args),
|
||||
errorMessage
|
||||
errorMessage || Alpine.store('global').t('operationFailed')
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -26,7 +26,7 @@ window.ModelConfigUtils.updateModelConfig = async function(modelId, configUpdate
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to update model config');
|
||||
throw new Error(store.t('failedToUpdateModelConfig'));
|
||||
}
|
||||
|
||||
// Optimistic update of local state
|
||||
@@ -38,5 +38,5 @@ window.ModelConfigUtils.updateModelConfig = async function(modelId, configUpdate
|
||||
|
||||
// Recompute quota rows to reflect changes
|
||||
dataStore.computeQuotaRows();
|
||||
}, 'Failed to update model config');
|
||||
}, Alpine.store('global').t('failedToUpdateModelConfig'));
|
||||
};
|
||||
|
||||
@@ -14,12 +14,13 @@ window.Validators = window.Validators || {};
|
||||
*/
|
||||
window.Validators.validateRange = function(value, min, max, fieldName = 'Value') {
|
||||
const numValue = Number(value);
|
||||
const t = Alpine.store('global').t;
|
||||
|
||||
if (isNaN(numValue)) {
|
||||
return {
|
||||
isValid: false,
|
||||
value: min,
|
||||
error: `${fieldName} must be a valid number`
|
||||
error: t('mustBeValidNumber', { fieldName })
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,7 +28,7 @@ window.Validators.validateRange = function(value, min, max, fieldName = 'Value')
|
||||
return {
|
||||
isValid: false,
|
||||
value: min,
|
||||
error: `${fieldName} must be at least ${min}`
|
||||
error: t('mustBeAtLeast', { fieldName, min })
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,7 +36,7 @@ window.Validators.validateRange = function(value, min, max, fieldName = 'Value')
|
||||
return {
|
||||
isValid: false,
|
||||
value: max,
|
||||
error: `${fieldName} must be at most ${max}`
|
||||
error: t('mustBeAtMost', { fieldName, max })
|
||||
};
|
||||
}
|
||||
|
||||
@@ -64,12 +65,13 @@ window.Validators.validatePort = function(port) {
|
||||
*/
|
||||
window.Validators.validateNotEmpty = function(value, fieldName = 'Field') {
|
||||
const trimmedValue = String(value || '').trim();
|
||||
const t = Alpine.store('global').t;
|
||||
|
||||
if (trimmedValue.length === 0) {
|
||||
return {
|
||||
isValid: false,
|
||||
value: trimmedValue,
|
||||
error: `${fieldName} cannot be empty`
|
||||
error: t('cannotBeEmpty', { fieldName })
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,7 +108,7 @@ window.Validators.validateBoolean = function(value) {
|
||||
return {
|
||||
isValid: false,
|
||||
value: false,
|
||||
error: 'Value must be true or false'
|
||||
error: Alpine.store('global').t('mustBeTrueOrFalse')
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user