- Add i18n (English + 简体中文) with a locale store, $t() magic, and a language toggle (persisted) - Add light/dark/system theme switching via a theme store and [data-theme] token overrides - Set the theme before paint to avoid a flash of the wrong theme - Add frontend version constant web/version.js (window.YDROPBX_VERSION) shown in the app footer - Update AGENTS.md version-control convention to mention web/version.js - Bump version to 0.1.1 across frontend, webui changelog, and backend constant
121 lines
3.8 KiB
JavaScript
121 lines
3.8 KiB
JavaScript
/**
|
|
* Internationalization (i18n).
|
|
*
|
|
* - `window.I18n` holds the message catalogs (currently English + 简体中文).
|
|
* - The `locale` store owns the active language, persists it to localStorage,
|
|
* and exposes `t(key)`.
|
|
* - `$t('key')` is registered as an Alpine magic so templates can translate
|
|
* inline (e.g. `x-text="$t('nav.logout')"`).
|
|
*
|
|
* Because `t()` reads the reactive `lang`, any string bound through it updates
|
|
* automatically when the language changes.
|
|
*/
|
|
window.I18n = {
|
|
en: {
|
|
'app.name': 'yDropbox',
|
|
'nav.logout': 'Logout',
|
|
'inbox': 'Inbox',
|
|
'outbox': 'Outbox',
|
|
'dropzone.click': 'Click to upload',
|
|
'dropzone.rest': 'or drag and drop',
|
|
'empty.inbox': 'No files in inbox',
|
|
'empty.outbox': 'No files in outbox',
|
|
'action.download': 'Download',
|
|
'action.share': 'Share',
|
|
'action.delete': 'Delete',
|
|
'toast.uploaded': 'File uploaded',
|
|
'toast.deleted': 'File deleted',
|
|
'toast.loadFailed': 'Failed to load files',
|
|
'toast.shareCopied': 'Share link copied to clipboard',
|
|
'confirm.deleteTitle': 'Delete file',
|
|
'confirm.deleteMessage': 'This file will be permanently removed. Continue?',
|
|
'confirm.cancel': 'Cancel',
|
|
'confirm.delete': 'Delete',
|
|
'confirm.confirm': 'Confirm',
|
|
'theme.light': 'Light',
|
|
'theme.dark': 'Dark',
|
|
'theme.system': 'System',
|
|
'lang.label': 'Language',
|
|
'login.subtitle': 'Enter your access token to continue',
|
|
'login.token': 'Access Token',
|
|
'login.submit': 'Sign In',
|
|
'login.error': 'Invalid token. Please try again.',
|
|
},
|
|
zh: {
|
|
'app.name': 'yDropbox',
|
|
'nav.logout': '退出登录',
|
|
'inbox': '收件箱',
|
|
'outbox': '发件箱',
|
|
'dropzone.click': '点击上传',
|
|
'dropzone.rest': '或拖拽文件到此处',
|
|
'empty.inbox': '收件箱暂无文件',
|
|
'empty.outbox': '发件箱暂无文件',
|
|
'action.download': '下载',
|
|
'action.share': '分享',
|
|
'action.delete': '删除',
|
|
'toast.uploaded': '文件已上传',
|
|
'toast.deleted': '文件已删除',
|
|
'toast.loadFailed': '文件加载失败',
|
|
'toast.shareCopied': '分享链接已复制到剪贴板',
|
|
'confirm.deleteTitle': '删除文件',
|
|
'confirm.deleteMessage': '该文件将被永久删除,是否继续?',
|
|
'confirm.cancel': '取消',
|
|
'confirm.delete': '删除',
|
|
'confirm.confirm': '确认',
|
|
'theme.light': '浅色',
|
|
'theme.dark': '深色',
|
|
'theme.system': '跟随系统',
|
|
'lang.label': '语言',
|
|
'login.subtitle': '请输入访问令牌以继续',
|
|
'login.token': '访问令牌',
|
|
'login.submit': '登录',
|
|
'login.error': '令牌无效,请重试。',
|
|
},
|
|
};
|
|
|
|
(function () {
|
|
function register() {
|
|
Alpine.store('locale', {
|
|
lang: 'en',
|
|
|
|
init() {
|
|
let saved = null;
|
|
try {
|
|
saved = localStorage.getItem('yd.locale');
|
|
} catch (e) {}
|
|
const browser = (navigator.language || 'en').toLowerCase().startsWith('zh') ? 'zh' : 'en';
|
|
this.lang = saved || browser;
|
|
this._applyLangAttr();
|
|
},
|
|
|
|
set(lang) {
|
|
this.lang = lang;
|
|
try {
|
|
localStorage.setItem('yd.locale', lang);
|
|
} catch (e) {}
|
|
this._applyLangAttr();
|
|
},
|
|
|
|
toggle() {
|
|
this.set(this.lang === 'en' ? 'zh' : 'en');
|
|
},
|
|
|
|
t(key) {
|
|
const dict = window.I18n[this.lang] || window.I18n.en;
|
|
if (dict && dict[key] != null) return dict[key];
|
|
if (window.I18n.en[key] != null) return window.I18n.en[key];
|
|
return key;
|
|
},
|
|
|
|
_applyLangAttr() {
|
|
document.documentElement.setAttribute('lang', this.lang === 'zh' ? 'zh-CN' : 'en');
|
|
},
|
|
});
|
|
|
|
Alpine.magic('t', () => (key) => Alpine.store('locale').t(key));
|
|
}
|
|
|
|
if (window.Alpine) register();
|
|
else document.addEventListener('alpine:init', register);
|
|
})();
|