Files
nodecrypt/client/js/util.i18n.js
T
2025-06-01 00:00:10 +08:00

334 lines
11 KiB
JavaScript

// Simple i18n utility for NodeCrypt
// NodeCrypt 简单国际化工具
// Language definitions
// 语言定义
const LANGUAGES = {
en: {
code: 'en',
name: 'English',
flag: '🇺🇸',
translations: {
// Login and main UI
'ui.enter_node': 'Enter a Node',
'ui.username': 'Username',
'ui.node_name': 'Node Name',
'ui.node_password': 'Node Password',
'ui.optional': '(optional)',
'ui.enter': 'ENTER',
'ui.connecting': 'Connecting...',
'ui.node_exists': 'Node already exists',
'ui.my_name': 'My Name',
'ui.members': 'Members',
'ui.message': 'Message',
'ui.private_message_to': 'Private Message to',
'ui.me': ' (me)',
'ui.anonymous': 'Anonymous',
'ui.start_private_chat': 'Select for private chat',
// Settings panel
'settings.title': 'Settings',
'settings.notification': 'Notification Settings',
'settings.theme': 'Theme Settings',
'settings.language': 'Language Settings',
'settings.desktop_notifications': 'Desktop Notifications',
'settings.sound_notifications': 'Sound Notifications',
'settings.language_switch': 'Language',
'settings.chinese': 'Chinese',
'settings.english': 'English',
// File upload and transfer
'file.selected_files': 'Selected Files',
'file.clear_all': 'Clear All',
'file.cancel': 'Cancel',
'file.send_files': 'Send Files', 'file.sending': 'Sending',
'file.receiving': 'Receiving',
'file.files': 'files',
'file.total': 'Total',
'file.files_selected': '{count} files selected, {size} total',
'file.upload_files': 'Upload Files',
'file.attach_file': 'Attach file',
'file.no_password_required': 'No password required',
'file.drag_drop': 'Drag and drop files here',
'file.or': 'or',
'file.browse_files': 'browse files',
// Notifications and messages
'notification.enabled': 'Notifications enabled',
'notification.alert_here': 'You will receive alerts here.',
'notification.not_supported': 'Notifications are not supported by your browser.',
'notification.allow_browser': 'Please allow notifications in your browser settings.',
'notification.image': '[image]',
'notification.private': '(Private)',
// Actions and menu
'action.share': 'Share',
'action.exit': 'Exit',
'action.emoji': 'Emoji',
'action.settings': 'Settings',
'action.back': 'Back',
'action.copied': 'Copied to clipboard!',
'action.share_copied': 'Share link copied!',
'action.copy_failed': 'Copy failed, text:',
'action.copy_url_failed': 'Copy failed, url:',
'action.nothing_to_copy': 'Nothing to copy',
'action.copy_not_supported': 'Copy not supported in this environment',
'action.action_failed': 'Action failed. Please try again.',
'action.cannot_share': 'Cannot share:',
// System messages
'system.security_warning': '⚠️ This link uses an old format. Room data is not encrypted.',
'system.file_send_failed': 'Failed to send files:',
'system.joined': 'joined the conversation',
'system.left': 'left the conversation',
'system.secured': 'connection secured',
'system.private_message_failed': 'Cannot send private message to',
'system.private_file_failed': 'Cannot send private file to',
'system.user_not_connected': 'User might not be fully connected.',
}
},
zh: {
code: 'zh',
name: '中文',
flag: '🇨🇳',
translations: {
// Login and main UI
'ui.enter_node': '进入新的节点',
'ui.username': '用户名',
'ui.node_name': '节点名称',
'ui.node_password': '节点密码',
'ui.optional': '(可选)',
'ui.enter': '确定',
'ui.connecting': '连接中...',
'ui.node_exists': '此节点已存在',
'ui.my_name': '我的名字',
'ui.members': '在线成员',
'ui.message': '消息',
'ui.private_message_to': '私信给',
'ui.me': '(我)',
'ui.anonymous': '匿名用户',
'ui.start_private_chat': '选择用户开始私信',
// Settings panel
'settings.title': '设置',
'settings.notification': '通知设置',
'settings.theme': '主题设置',
'settings.language': '语言设置',
'settings.desktop_notifications': '桌面通知',
'settings.sound_notifications': '声音通知',
'settings.language_switch': '语言',
'settings.chinese': '中文',
'settings.english': 'English',
// File upload and transfer
'file.selected_files': '已选择的文件',
'file.clear_all': '清空所有',
'file.cancel': '取消',
'file.send_files': '发送文件', 'file.sending': '发送中',
'file.receiving': '接收中',
'file.files': '个文件',
'file.total': '总计',
'file.files_selected': '选中 {count} 个文件,总计 {size}',
'file.upload_files': '上传文件',
'file.attach_file': '附加文件',
'file.no_password_required': '无需密码',
'file.drag_drop': '拖拽文件到此处',
'file.or': '或',
'file.browse_files': '浏览文件',
// Notifications and messages
'notification.enabled': '通知已启用',
'notification.alert_here': '您将在此处收到通知。',
'notification.not_supported': '您的浏览器不支持通知功能。',
'notification.allow_browser': '请在浏览器设置中允许通知。',
'notification.image': '[图片]',
'notification.private': '(私信)',
// Actions and menu
'action.share': '分享',
'action.exit': '退出',
'action.emoji': '表情',
'action.settings': '设置',
'action.back': '返回',
'action.copied': '已复制到剪贴板!',
'action.share_copied': '分享链接已复制!',
'action.copy_failed': '复制失败,文本:',
'action.copy_url_failed': '复制失败,链接:',
'action.nothing_to_copy': '没有内容可复制',
'action.copy_not_supported': '此环境不支持复制功能',
'action.action_failed': '操作失败,请重试。',
'action.cannot_share': '无法分享:',
// System messages
'system.security_warning': '⚠️ 此链接使用旧格式,房间数据未加密。',
'system.file_send_failed': '文件发送失败:',
'system.joined': '加入了对话',
'system.left': '离开了对话',
'system.secured': '已建立端到端安全连接',
'system.private_message_failed': '无法发送私信给',
'system.private_file_failed': '无法发送私密文件给',
'system.user_not_connected': '用户可能未完全连接。',
}
}
};
// Current language
// 当前语言
let currentLanguage = 'en';
// Get translation for a key
// 获取翻译文本
export function t(key, fallback = key) {
const lang = LANGUAGES[currentLanguage];
if (lang && lang.translations && lang.translations[key]) {
return lang.translations[key];
}
return fallback;
}
// Set current language
// 设置当前语言
export function setLanguage(langCode) {
if (LANGUAGES[langCode]) {
currentLanguage = langCode;
// Update document language attribute
// 更新文档语言属性
document.documentElement.lang = langCode;
// Update static HTML texts
// 更新HTML中的静态文本
updateStaticTexts();
// Dispatch language change event for other components to listen
// 派发语言变更事件供其他组件监听
window.dispatchEvent(new CustomEvent('languageChange', {
detail: { language: langCode }
}));
}
}
// Get current language
// 获取当前语言
export function getCurrentLanguage() {
return currentLanguage;
}
// Get all available languages
// 获取所有可用语言
export function getAvailableLanguages() {
return Object.keys(LANGUAGES).map(code => ({
code,
name: LANGUAGES[code].name,
flag: LANGUAGES[code].flag
}));
}
// Initialize i18n with settings
// 根据设置初始化国际化
export function initI18n(settings) {
if (settings && settings.language) {
setLanguage(settings.language);
} else {
// Auto-detect browser language
// 自动检测浏览器语言
const browserLang = detectBrowserLanguage();
setLanguage(browserLang);
}
}
// Detect browser language and return supported language code
// 检测浏览器语言并返回支持的语言代码
function detectBrowserLanguage() {
const navigatorLang = navigator.language || navigator.userLanguage || 'en';
// Extract language code (e.g., 'zh-CN' -> 'zh', 'en-US' -> 'en')
const langCode = navigatorLang.split('-')[0].toLowerCase();
// Check if we support this language
if (LANGUAGES[langCode]) {
return langCode;
}
// Default fallback to English
return 'en';
}
// Update static HTML text elements
// 更新HTML中的静态文本元素
export function updateStaticTexts() {
// 如果DOM还没准备好,等待DOM准备好再更新
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => updateStaticTexts());
return;
}
// Update login title
const loginTitle = document.getElementById('login-title');
if (loginTitle) {
loginTitle.textContent = t('ui.enter_node', 'Enter a Node');
}
// Update login form content with new translations
const loginFormContainer = document.getElementById('login-form');
if (loginFormContainer) {
// Use a custom event to trigger form regeneration instead of dynamic import
// 使用自定义事件触发表单重新生成,而不是动态导入
window.dispatchEvent(new CustomEvent('regenerateLoginForm'));
}
// Update sidebar username label
const sidebarUsername = document.getElementById('sidebar-username');
if (sidebarUsername) {
// Use a custom event to update sidebar username instead of dynamic import
// 使用自定义事件更新侧边栏用户名,而不是动态导入
window.dispatchEvent(new CustomEvent('updateSidebarUsername'));
}
// Update "Enter a Node" text in sidebar
const joinRoomText = document.getElementById('join-room-text');
if (joinRoomText) {
joinRoomText.textContent = t('ui.enter_node', 'Enter a Node');
}
// Update Members title in rightbar
const membersTitle = document.getElementById('members-title');
if (membersTitle) {
membersTitle.textContent = t('ui.members', 'Members');
}
// Update settings title
const settingsTitle = document.getElementById('settings-title');
if (settingsTitle) {
settingsTitle.textContent = t('settings.title', 'Settings');
}
// Update message placeholder
const messagePlaceholder = document.querySelector('.input-field-placeholder');
if (messagePlaceholder) {
messagePlaceholder.textContent = t('ui.message', 'Message');
}
// Update attach button title
const attachBtn = document.querySelector('.chat-attach-btn');
if (attachBtn) {
attachBtn.title = t('file.attach_file', 'Attach file');
}
// Update emoji button title
const emojiBtn = document.querySelector('.chat-emoji-btn');
if (emojiBtn) {
emojiBtn.title = t('action.emoji', 'Emoji');
}
// Update settings button title
const settingsBtn = document.getElementById('settings-btn');
if (settingsBtn) {
settingsBtn.title = t('action.settings', 'Settings');
settingsBtn.setAttribute('aria-label', t('action.settings', 'Settings'));
}
// Update back button title
const backBtn = document.getElementById('settings-back-btn');
if (backBtn) {
backBtn.title = t('action.back', 'Back');
backBtn.setAttribute('aria-label', t('action.back', 'Back'));
}
}