/** * 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); })();