diff --git a/.env.template b/.env.template index 3166b80..81849de 100644 --- a/.env.template +++ b/.env.template @@ -61,4 +61,8 @@ API_KEY_SECRET=call_me_api_key_secret # change me # API endpoint for fetching random images (e.g., Unsplash or other services): https://unsplash.com/developers # RANDOM_IMAGE_URL='https://api.unsplash.com/photos/random?query=nature&orientation=landscape&client_id=YOUR-ACCESS-KEY'; -RANDOM_IMAGE_URL='' \ No newline at end of file +RANDOM_IMAGE_URL='' + +# I18N Internationalization + +I18N_ENABLED=false # true or false \ No newline at end of file diff --git a/app/locales/ar.json b/app/locales/ar.json new file mode 100644 index 0000000..c147d80 --- /dev/null +++ b/app/locales/ar.json @@ -0,0 +1,77 @@ +{ + "appName": "Call-me", + "appTitle": "Call-me - مكالمات فيديو فورية", + "appDescription": "خيارك للمكالمات المرئية الفورية!", + "signIn": { + "title": "تسجيل الدخول", + "username": "أدخل اسم المستخدم", + "button": "تسجيل الدخول", + "enterUsername": "يرجى إدخال اسم المستخدم" + }, + "room": { + "sessionTime": "مدة الجلسة", + "localUsername": "أنت", + "remoteUsername": "مستخدم بعيد", + "waiting": "بانتظار انضمام شخص ما...", + "connecting": "جارٍ الاتصال...", + "userJoined": "انضم __username__ إلى المكالمة", + "userLeft": "غادر __username__ المكالمة", + "users": "المستخدمون", + "chat": "الدردشة", + "searchUsers": "ابحث عن مستخدمين..." + }, + "controls": { + "microphone": "الميكروفون", + "camera": "الكاميرا", + "screenShare": "مشاركة الشاشة", + "endCall": "إنهاء المكالمة", + "settings": "الإعدادات", + "fullscreen": "ملء الشاشة" + }, + "messages": { + "microphoneEnabled": "تم تفعيل الميكروفون", + "microphoneDisabled": "تم تعطيل الميكروفون", + "cameraEnabled": "تم تفعيل الكاميرا", + "cameraDisabled": "تم تعطيل الكاميرا", + "screenShareStarted": "بدأت مشاركة الشاشة", + "screenShareStopped": "توقفت مشاركة الشاشة", + "callEnded": "انتهت المكالمة", + "connectionFailed": "فشل الاتصال", + "permissionDenied": "تم رفض الإذن", + "error": "حدث خطأ", + "copied": "تم النسخ إلى الحافظة", + "invalidPassword": "كلمة مرور غير صحيحة" + }, + "errors": { + "noUsername": "اسم المستخدم مطلوب", + "connectionLost": "انقطع الاتصال", + "mediaDevices": "تعذر الوصول إلى أجهزة الوسائط", + "noSupport": "متصفحك لا يدعم هذه الميزة" + }, + "settings": { + "language": "اللغة", + "selectLanguage": "اختر اللغة", + "audioInput": "الميكروفون", + "videoInput": "الكاميرا", + "audioOutput": "مكبر الصوت", + "resolution": "دقة الفيديو", + "save": "حفظ", + "cancel": "إلغاء", + "settings": "الإعدادات", + "testDevices": "اختبار الأجهزة", + "refresh": "تحديث", + "saveMessages": "حفظ الرسائل", + "clearAll": "مسح الكل" + }, + "chat": { + "addEmoji": "إضافة إيموجي", + "typeMessage": "اكتب رسالة...", + "sendMessage": "إرسال رسالة" + }, + "api": { + "unauthorized": "وصول غير مصرح به", + "invalidApiKey": "مفتاح API غير صالح", + "serverError": "خطأ في الخادم", + "notFound": "المورد غير موجود" + } +} diff --git a/app/locales/hi.json b/app/locales/hi.json new file mode 100644 index 0000000..83efe63 --- /dev/null +++ b/app/locales/hi.json @@ -0,0 +1,77 @@ +{ + "appName": "Call-me", + "appTitle": "Call-me - तुरंत वीडियो कॉल", + "appDescription": "तुरंत वीडियो कॉल के लिए आपका पसंदीदा विकल्प!", + "signIn": { + "title": "साइन इन", + "username": "उपयोगकर्ता नाम दर्ज करें", + "button": "साइन इन", + "enterUsername": "कृपया अपना उपयोगकर्ता नाम दर्ज करें" + }, + "room": { + "sessionTime": "सत्र समय", + "localUsername": "आप", + "remoteUsername": "दूरस्थ उपयोगकर्ता", + "waiting": "किसी के जुड़ने का इंतज़ार...", + "connecting": "कनेक्ट हो रहा है...", + "userJoined": "__username__ कॉल में शामिल हुआ", + "userLeft": "__username__ कॉल से निकल गया", + "users": "उपयोगकर्ता", + "chat": "चैट", + "searchUsers": "उपयोगकर्ता खोजें..." + }, + "controls": { + "microphone": "माइक्रोफ़ोन", + "camera": "कैमरा", + "screenShare": "स्क्रीन साझा करें", + "endCall": "कॉल समाप्त करें", + "settings": "सेटिंग्स", + "fullscreen": "फुलस्क्रीन" + }, + "messages": { + "microphoneEnabled": "माइक्रोफ़ोन चालू", + "microphoneDisabled": "माइक्रोफ़ोन बंद", + "cameraEnabled": "कैमरा चालू", + "cameraDisabled": "कैमरा बंद", + "screenShareStarted": "स्क्रीन साझा करना शुरू हुआ", + "screenShareStopped": "स्क्रीन साझा करना बंद हुआ", + "callEnded": "कॉल समाप्त हुई", + "connectionFailed": "कनेक्शन विफल", + "permissionDenied": "अनुमति अस्वीकृत", + "error": "एक त्रुटि हुई", + "copied": "क्लिपबोर्ड पर कॉपी किया गया", + "invalidPassword": "अमान्य पासवर्ड" + }, + "errors": { + "noUsername": "उपयोगकर्ता नाम आवश्यक है", + "connectionLost": "कनेक्शन खो गया", + "mediaDevices": "मीडिया डिवाइस तक पहुँच नहीं हो सकी", + "noSupport": "आपका ब्राउज़र इस सुविधा का समर्थन नहीं करता" + }, + "settings": { + "language": "भाषा", + "selectLanguage": "भाषा चुनें", + "audioInput": "माइक्रोफ़ोन", + "videoInput": "कैमरा", + "audioOutput": "स्पीकर", + "resolution": "वीडियो रिज़ॉल्यूशन", + "save": "सहेजें", + "cancel": "रद्द करें", + "settings": "सेटिंग्स", + "testDevices": "डिवाइस जाँचें", + "refresh": "रीफ़्रेश", + "saveMessages": "संदेश सहेजें", + "clearAll": "सभी साफ़ करें" + }, + "chat": { + "addEmoji": "इमोजी जोड़ें", + "typeMessage": "संदेश लिखें...", + "sendMessage": "संदेश भेजें" + }, + "api": { + "unauthorized": "अनधिकृत पहुँच", + "invalidApiKey": "अमान्य API कुंजी", + "serverError": "सर्वर त्रुटि", + "notFound": "संसाधन नहीं मिला" + } +} diff --git a/app/locales/ja.json b/app/locales/ja.json new file mode 100644 index 0000000..6dc8d74 --- /dev/null +++ b/app/locales/ja.json @@ -0,0 +1,77 @@ +{ + "appName": "Call-me", + "appTitle": "Call-me - 即時ビデオ通話", + "appDescription": "即時ビデオ通話に最適!", + "signIn": { + "title": "サインイン", + "username": "ユーザー名を入力", + "button": "サインイン", + "enterUsername": "ユーザー名を入力してください" + }, + "room": { + "sessionTime": "セッション時間", + "localUsername": "あなた", + "remoteUsername": "リモートユーザー", + "waiting": "参加者を待っています...", + "connecting": "接続中...", + "userJoined": "__username__ が通話に参加しました", + "userLeft": "__username__ が通話から退出しました", + "users": "ユーザー", + "chat": "チャット", + "searchUsers": "ユーザーを検索..." + }, + "controls": { + "microphone": "マイク", + "camera": "カメラ", + "screenShare": "画面共有", + "endCall": "通話終了", + "settings": "設定", + "fullscreen": "全画面" + }, + "messages": { + "microphoneEnabled": "マイクを有効にしました", + "microphoneDisabled": "マイクを無効にしました", + "cameraEnabled": "カメラを有効にしました", + "cameraDisabled": "カメラを無効にしました", + "screenShareStarted": "画面共有を開始しました", + "screenShareStopped": "画面共有を停止しました", + "callEnded": "通話が終了しました", + "connectionFailed": "接続に失敗しました", + "permissionDenied": "権限が拒否されました", + "error": "エラーが発生しました", + "copied": "クリップボードにコピーしました", + "invalidPassword": "パスワードが無効です" + }, + "errors": { + "noUsername": "ユーザー名は必須です", + "connectionLost": "接続が切断されました", + "mediaDevices": "メディアデバイスにアクセスできませんでした", + "noSupport": "お使いのブラウザはこの機能をサポートしていません" + }, + "settings": { + "language": "言語", + "selectLanguage": "言語を選択", + "audioInput": "マイク", + "videoInput": "カメラ", + "audioOutput": "スピーカー", + "resolution": "ビデオ解像度", + "save": "保存", + "cancel": "キャンセル", + "settings": "設定", + "testDevices": "デバイスをテスト", + "refresh": "更新", + "saveMessages": "メッセージを保存", + "clearAll": "すべてクリア" + }, + "chat": { + "addEmoji": "絵文字を追加", + "typeMessage": "メッセージを入力...", + "sendMessage": "メッセージを送信" + }, + "api": { + "unauthorized": "未承認のアクセス", + "invalidApiKey": "無効な API キー", + "serverError": "サーバーエラー", + "notFound": "リソースが見つかりません" + } +} diff --git a/app/locales/pt.json b/app/locales/pt.json new file mode 100644 index 0000000..c78dedb --- /dev/null +++ b/app/locales/pt.json @@ -0,0 +1,77 @@ +{ + "appName": "Call-me", + "appTitle": "Call-me - Chamadas de vídeo instantâneas", + "appDescription": "Sua opção para chamadas de vídeo instantâneas!", + "signIn": { + "title": "Entrar", + "username": "Digite o nome de usuário", + "button": "Entrar", + "enterUsername": "Por favor, digite seu nome de usuário" + }, + "room": { + "sessionTime": "Tempo da sessão", + "localUsername": "Você", + "remoteUsername": "Usuário remoto", + "waiting": "Aguardando alguém entrar...", + "connecting": "Conectando...", + "userJoined": "__username__ entrou na chamada", + "userLeft": "__username__ saiu da chamada", + "users": "Usuários", + "chat": "Chat", + "searchUsers": "Pesquisar usuários..." + }, + "controls": { + "microphone": "Microfone", + "camera": "Câmera", + "screenShare": "Compartilhar tela", + "endCall": "Encerrar chamada", + "settings": "Configurações", + "fullscreen": "Tela cheia" + }, + "messages": { + "microphoneEnabled": "Microfone ativado", + "microphoneDisabled": "Microfone desativado", + "cameraEnabled": "Câmera ativada", + "cameraDisabled": "Câmera desativada", + "screenShareStarted": "Compartilhamento de tela iniciado", + "screenShareStopped": "Compartilhamento de tela interrompido", + "callEnded": "Chamada encerrada", + "connectionFailed": "Falha na conexão", + "permissionDenied": "Permissão negada", + "error": "Ocorreu um erro", + "copied": "Copiado para a área de transferência", + "invalidPassword": "Senha inválida" + }, + "errors": { + "noUsername": "Nome de usuário é obrigatório", + "connectionLost": "Conexão perdida", + "mediaDevices": "Não foi possível acessar os dispositivos de mídia", + "noSupport": "Seu navegador não suporta este recurso" + }, + "settings": { + "language": "Idioma", + "selectLanguage": "Selecionar idioma", + "audioInput": "Microfone", + "videoInput": "Câmera", + "audioOutput": "Alto-falante", + "resolution": "Resolução de vídeo", + "save": "Salvar", + "cancel": "Cancelar", + "settings": "Configurações", + "testDevices": "Testar dispositivos", + "refresh": "Atualizar", + "saveMessages": "Salvar mensagens", + "clearAll": "Limpar tudo" + }, + "chat": { + "addEmoji": "Adicionar emoji", + "typeMessage": "Digite uma mensagem...", + "sendMessage": "Enviar mensagem" + }, + "api": { + "unauthorized": "Acesso não autorizado", + "invalidApiKey": "Chave de API inválida", + "serverError": "Erro do servidor", + "notFound": "Recurso não encontrado" + } +} diff --git a/app/locales/ru.json b/app/locales/ru.json new file mode 100644 index 0000000..99fa890 --- /dev/null +++ b/app/locales/ru.json @@ -0,0 +1,77 @@ +{ + "appName": "Call-me", + "appTitle": "Call-me — мгновенные видеозвонки", + "appDescription": "Ваш выбор для мгновенных видеозвонков!", + "signIn": { + "title": "Войти", + "username": "Введите имя пользователя", + "button": "Войти", + "enterUsername": "Пожалуйста, введите имя пользователя" + }, + "room": { + "sessionTime": "Время сессии", + "localUsername": "Вы", + "remoteUsername": "Удалённый пользователь", + "waiting": "Ожидание подключения...", + "connecting": "Подключение...", + "userJoined": "__username__ присоединился к звонку", + "userLeft": "__username__ покинул звонок", + "users": "Пользователи", + "chat": "Чат", + "searchUsers": "Поиск пользователей..." + }, + "controls": { + "microphone": "Микрофон", + "camera": "Камера", + "screenShare": "Демонстрация экрана", + "endCall": "Завершить звонок", + "settings": "Настройки", + "fullscreen": "Полный экран" + }, + "messages": { + "microphoneEnabled": "Микрофон включён", + "microphoneDisabled": "Микрофон выключен", + "cameraEnabled": "Камера включена", + "cameraDisabled": "Камера выключена", + "screenShareStarted": "Демонстрация экрана началась", + "screenShareStopped": "Демонстрация экрана остановлена", + "callEnded": "Звонок завершён", + "connectionFailed": "Не удалось подключиться", + "permissionDenied": "Доступ запрещён", + "error": "Произошла ошибка", + "copied": "Скопировано в буфер обмена", + "invalidPassword": "Неверный пароль" + }, + "errors": { + "noUsername": "Имя пользователя обязательно", + "connectionLost": "Соединение потеряно", + "mediaDevices": "Не удалось получить доступ к медиа-устройствам", + "noSupport": "Ваш браузер не поддерживает эту функцию" + }, + "settings": { + "language": "Язык", + "selectLanguage": "Выберите язык", + "audioInput": "Микрофон", + "videoInput": "Камера", + "audioOutput": "Динамик", + "resolution": "Разрешение видео", + "save": "Сохранить", + "cancel": "Отмена", + "settings": "Настройки", + "testDevices": "Проверить устройства", + "refresh": "Обновить", + "saveMessages": "Сохранить сообщения", + "clearAll": "Очистить всё" + }, + "chat": { + "addEmoji": "Добавить эмодзи", + "typeMessage": "Введите сообщение...", + "sendMessage": "Отправить сообщение" + }, + "api": { + "unauthorized": "Доступ запрещён", + "invalidApiKey": "Неверный API-ключ", + "serverError": "Ошибка сервера", + "notFound": "Ресурс не найден" + } +} diff --git a/app/locales/zh.json b/app/locales/zh.json new file mode 100644 index 0000000..31a9e6e --- /dev/null +++ b/app/locales/zh.json @@ -0,0 +1,77 @@ +{ + "appName": "Call-me", + "appTitle": "Call-me - 即时视频通话", + "appDescription": "即时视频通话的首选!", + "signIn": { + "title": "登录", + "username": "请输入用户名", + "button": "登录", + "enterUsername": "请输入您的用户名" + }, + "room": { + "sessionTime": "会话时长", + "localUsername": "你", + "remoteUsername": "远端用户", + "waiting": "等待有人加入...", + "connecting": "正在连接...", + "userJoined": "__username__ 加入了通话", + "userLeft": "__username__ 离开了通话", + "users": "用户", + "chat": "聊天", + "searchUsers": "搜索用户..." + }, + "controls": { + "microphone": "麦克风", + "camera": "摄像头", + "screenShare": "共享屏幕", + "endCall": "结束通话", + "settings": "设置", + "fullscreen": "全屏" + }, + "messages": { + "microphoneEnabled": "麦克风已开启", + "microphoneDisabled": "麦克风已关闭", + "cameraEnabled": "摄像头已开启", + "cameraDisabled": "摄像头已关闭", + "screenShareStarted": "已开始共享屏幕", + "screenShareStopped": "已停止共享屏幕", + "callEnded": "通话已结束", + "connectionFailed": "连接失败", + "permissionDenied": "权限被拒绝", + "error": "发生错误", + "copied": "已复制到剪贴板", + "invalidPassword": "密码无效" + }, + "errors": { + "noUsername": "必须填写用户名", + "connectionLost": "连接已断开", + "mediaDevices": "无法访问媒体设备", + "noSupport": "您的浏览器不支持此功能" + }, + "settings": { + "language": "语言", + "selectLanguage": "选择语言", + "audioInput": "麦克风", + "videoInput": "摄像头", + "audioOutput": "扬声器", + "resolution": "视频分辨率", + "save": "保存", + "cancel": "取消", + "settings": "设置", + "testDevices": "测试设备", + "refresh": "刷新", + "saveMessages": "保存消息", + "clearAll": "全部清除" + }, + "chat": { + "addEmoji": "添加表情", + "typeMessage": "输入消息...", + "sendMessage": "发送消息" + }, + "api": { + "unauthorized": "未授权访问", + "invalidApiKey": "API 密钥无效", + "serverError": "服务器错误", + "notFound": "资源未找到" + } +} diff --git a/app/server.js b/app/server.js index 00fbda4..f32e874 100755 --- a/app/server.js +++ b/app/server.js @@ -26,6 +26,26 @@ const PUBLIC_DIR = path.join(__dirname, '../', 'public'); // Home page/client const HOME = path.join(PUBLIC_DIR, '/index.html'); +// Locales directory location +const LOCALES_DIR = path.join(__dirname, 'locales'); + +function getAvailableLocales() { + try { + return fs + .readdirSync(LOCALES_DIR, { withFileTypes: true }) + .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.json')) + .map((dirent) => path.basename(dirent.name, '.json')) + .filter(Boolean) + .sort(); + } catch (error) { + log.warn('Unable to read locales directory', { + directory: LOCALES_DIR, + error: error.message, + }); + return []; + } +} + // Map to store connected users const users = new Map(); @@ -101,19 +121,49 @@ const corsOptions = { // Create Express application const app = express(); -// Configure i18n -i18n.configure({ - locales: ['en', 'es', 'fr', 'it', 'de'], - defaultLocale: 'en', - directory: path.join(__dirname, 'locales'), - objectNotation: true, - updateFiles: false, - syncFiles: false, - api: { - __: 'translate', - __n: 'translateN', - }, -}); +function configureI18n() { + const locales = getAvailableLocales(); + const effectiveLocales = locales.length > 0 ? locales : ['en']; + const defaultLocale = effectiveLocales.includes('en') ? 'en' : effectiveLocales[0] || 'en'; + + i18n.configure({ + locales: effectiveLocales, + defaultLocale: defaultLocale, + directory: LOCALES_DIR, + objectNotation: true, + updateFiles: false, + syncFiles: false, + api: { + __: 'translate', + __n: 'translateN', + }, + }); + + return { locales: effectiveLocales, defaultLocale }; +} + +// Configure i18n (dynamic locales from app/locales/*.json) +configureI18n(); + +// Optional: watch locales folder and reconfigure automatically (no restart) +if (process.env.I18N_WATCH === 'true') { + try { + let debounceTimer; + fs.watch(LOCALES_DIR, { persistent: false }, () => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => { + const state = configureI18n(); + log.info('i18n reconfigured', state); + }, 250); + }); + log.info('i18n watch enabled', { directory: LOCALES_DIR }); + } catch (error) { + log.warn('Unable to watch locales directory', { + directory: LOCALES_DIR, + error: error.message, + }); + } +} // Server configurations const port = process.env.PORT || 4000; @@ -203,17 +253,26 @@ app.get('/randomImage', async (req, res) => { } }); +// List available locales (derived from app/locales/*.json) +app.get('/locales', (req, res) => { + const locales = getAvailableLocales(); + res.json({ locales: locales.length > 0 ? locales : ['en'] }); +}); + // Get translations for a specific language app.get('/translations/:locale', (req, res) => { try { - const locale = req.params.locale || 'en'; - const validLocales = ['en', 'es', 'fr', 'it', 'de']; + const locale = String(req.params.locale || 'en').toLowerCase(); + const validLocales = getAvailableLocales(); - if (!validLocales.includes(locale)) { + if (validLocales.length > 0 && !validLocales.includes(locale)) { return res.status(400).json({ error: 'Invalid locale' }); } - const translationPath = path.join(__dirname, 'locales', `${locale}.json`); + const translationPath = path.join(LOCALES_DIR, `${locale}.json`); + if (!fs.existsSync(translationPath)) { + return res.status(400).json({ error: 'Invalid locale' }); + } const translations = JSON.parse(fs.readFileSync(translationPath, 'utf8')); res.json({ diff --git a/package-lock.json b/package-lock.json index 441d202..0c6dc68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "call-me", - "version": "1.2.88", + "version": "1.2.89", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "call-me", - "version": "1.2.88", + "version": "1.2.89", "license": "AGPLv3", "dependencies": { "@ngrok/ngrok": "1.7.0", - "axios": "^1.13.4", + "axios": "^1.13.5", "colors": "^1.4.0", "cors": "^2.8.6", "dotenv": "^17.2.4", @@ -366,13 +366,13 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", - "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -964,9 +964,9 @@ "license": "MIT" }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -984,9 +984,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", diff --git a/package.json b/package.json index b9c5224..7652ff7 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "call-me", - "version": "1.2.88", + "version": "1.2.89", "description": "Your Go-To for Instant Video Calls", "author": "Miroslav Pejic - miroslav.pejic.85@gmail.com", "license": "AGPLv3", @@ -20,7 +20,7 @@ }, "dependencies": { "@ngrok/ngrok": "1.7.0", - "axios": "^1.13.4", + "axios": "^1.13.5", "colors": "^1.4.0", "cors": "^2.8.6", "dotenv": "^17.2.4", diff --git a/public/i18n.js b/public/i18n.js index efcc8a6..c567a1b 100644 --- a/public/i18n.js +++ b/public/i18n.js @@ -6,8 +6,40 @@ const i18n = { currentLocale: 'en', translations: {}, defaultLocale: 'en', + availableLocales: [], }; +async function fetchAvailableLocales() { + try { + const response = await fetch('/locales'); + if (!response.ok) throw new Error('Failed to fetch locales'); + const data = await response.json(); + if (data && Array.isArray(data.locales) && data.locales.length > 0) { + return data.locales; + } + } catch (error) { + console.warn('Unable to fetch available locales, using fallback list', error); + } + return ['en', 'es', 'fr', 'it', 'de']; +} + +function getLocaleLabel(locale) { + const labels = { + en: '🇬🇧 English', + es: '🇪🇸 Español', + fr: '🇫🇷 Français', + it: '🇮🇹 Italiano', + de: '🇩🇪 Deutsch', + pt: '🇧🇷 Português', + ru: '🇷🇺 Русский', + ar: '🇸🇦 العربية', + hi: '🇮🇳 हिन्दी', + zh: '🇨🇳 中文', + ja: '🇯🇵 日本語', + }; + return labels[locale] || locale; +} + /** * Initialize i18n */ @@ -15,7 +47,8 @@ async function initI18n() { // Get saved locale from localStorage or use browser language or default const savedLocale = localStorage.getItem('locale'); const browserLocale = navigator.language.split('-')[0]; // Get 'en' from 'en-US' - const supportedLocales = ['en', 'es', 'fr', 'it', 'de']; + const supportedLocales = await fetchAvailableLocales(); + i18n.availableLocales = supportedLocales; // Determine which locale to use if (savedLocale && supportedLocales.includes(savedLocale)) { @@ -120,6 +153,17 @@ async function changeLanguage(locale) { function setupLanguageSelector() { const languageSelect = document.getElementById('languageSelect'); if (languageSelect) { + // Rebuild options dynamically + const locales = + Array.isArray(i18n.availableLocales) && i18n.availableLocales.length ? i18n.availableLocales : ['en']; + languageSelect.innerHTML = ''; + locales.forEach((locale) => { + const option = document.createElement('option'); + option.value = locale; + option.textContent = getLocaleLabel(locale); + languageSelect.appendChild(option); + }); + // Set current locale languageSelect.value = i18n.currentLocale; diff --git a/public/index.html b/public/index.html index e017b25..f0df2b0 100755 --- a/public/index.html +++ b/public/index.html @@ -268,13 +268,7 @@ - +