diff --git a/src/i18n/locales/de.ts b/src/i18n/locales/de.ts new file mode 100644 index 0000000..011c359 --- /dev/null +++ b/src/i18n/locales/de.ts @@ -0,0 +1,113 @@ +const de = { + lang: 'Deutsch', + + home_tagline: 'Wo Gespräche sich treffen – serverlos.', + home_description: + 'Erstelle in Sekunden einen privaten Videoraum. Reines Peer-to-Peer-WebRTC – keine Konten, keine Server, keine Vermittler. Nur du, deine Leute und ein Code aus sechs Buchstaben.', + home_your_name: 'Dein Name', + home_host: 'Neues Meeting starten', + home_or_join: 'oder beitreten', + home_meeting_code: 'Meeting-Code', + home_meeting_code_placeholder: 'ABCXYZ', + home_join: 'Beitreten', + home_name_required_hint: 'Gib deinen Namen ein, um zu hosten oder beizutreten.', + home_error_name: 'Bitte gib deinen Namen ein.', + home_error_code: 'Der Meeting-Code muss aus 6 Buchstaben bestehen.', + home_footnote: 'Peer-to-Peer über WebRTC. Keine Konten, keine Server.', + + footer_author: 'Autor', + footer_github: 'GitHub', + footer_feedback: 'Feedback', + + meeting_invalid_code: 'Ungültiger Meeting-Code', + meeting_back_home: 'Zurück zur Startseite', + meeting_join_title: 'Meeting beitreten', + meeting_enter_name: 'Bitte gib deinen Namen ein, um fortzufahren.', + meeting_your_name: 'Dein Name', + meeting_join: 'Beitreten', + + meeting_preparing: 'Kamera und Mikrofon werden vorbereitet…', + meeting_starting: 'Meeting wird gestartet…', + meeting_joining: 'Meeting wird beigetreten…', + + meeting_error_title: 'Beitritt zum Meeting fehlgeschlagen', + meeting_error_default: 'Ein Fehler ist aufgetreten.', + meeting_error_unavailable_id: + 'Dieser Meeting-Code wird bereits verwendet. Versuche einen anderen.', + meeting_error_peer_unavailable: 'Meeting nicht gefunden.', + meeting_error_start: 'Meeting konnte nicht gestartet werden.', + + meeting_ended_host: 'Der Host hat das Meeting beendet', + meeting_ended_self: 'Du hast das Meeting verlassen', + meeting_ended_kicked: 'Du wurdest vom Host aus dem Meeting entfernt', + + meeting_share_invite_aria: 'Einladung teilen', + meeting_person: 'Person', + meeting_people: 'Personen', + + meeting_end_for_everyone: 'Meeting für alle beenden?', + meeting_leave_title: 'Meeting verlassen?', + meeting_end_for_everyone_body: + 'Du bist der Host. Wenn du das Meeting beendest, werden alle getrennt.', + meeting_leave_body: 'Du wirst von diesem Meeting getrennt.', + meeting_cancel: 'Abbrechen', + meeting_end: 'Meeting beenden', + meeting_leave: 'Verlassen', + + chat_title: 'Chat', + chat_close: 'Chat schließen', + chat_send: 'Nachricht senden', + chat_emoji: 'Emoji einfügen', + chat_placeholder: 'Nachricht…', + chat_empty: 'Noch keine Nachrichten.', + chat_you: 'Du', + chat_system_joined: (name: string) => `${name} ist beigetreten`, + chat_system_left: (name: string) => `${name} hat das Meeting verlassen`, + + controls_mute: 'Stummschalten', + controls_unmute: 'Stummschaltung aufheben', + controls_stop_video: 'Video stoppen', + controls_start_video: 'Video starten', + controls_chat: 'Chat', + controls_share: 'Einladung teilen', + controls_participants: 'Teilnehmer', + controls_leave: 'Verlassen', + + participants_title: 'Teilnehmer', + participants_close: 'Teilnehmer schließen', + participants_host: 'Host', + participants_you: 'Du', + participants_kick: 'Aus Meeting entfernen', + participants_kick_confirm_title: 'Teilnehmer entfernen?', + participants_kick_confirm_body: (name: string) => + `${name} wird aus diesem Meeting entfernt.`, + participants_kick_confirm: 'Entfernen', + participants_system_kicked: (name: string) => `${name} wurde entfernt`, + + rail_resize: 'Panels anpassen', + + share_title: 'Andere einladen', + share_meeting_code: 'Meeting-Code', + share_copy_code: 'Code kopieren', + share_copy_link: 'Link kopieren', + share_copied: 'Kopiert', + share_invite_link: 'Einladungslink', + share_via: 'Teilen über', + share_more: 'Mehr…', + share_more_aria: 'Weitere Freigabeoptionen', + share_done: 'Fertig', + share_on: (network: string) => `Auf ${network} teilen`, + share_subject: 'Tritt meinem Meeting bei', + share_text: (code: string) => `Tritt meinem Meeting bei (Code: ${code})`, + + tile_you: '(du)', + tile_host: 'Host', + + theme_switch_to: (mode: string) => `In den ${mode} wechseln`, + theme_dark: 'Dunkelmodus', + theme_light: 'Hellmodus', + + language_change: 'Sprache ändern', +} as const; + +export default de; diff --git a/src/i18n/locales/ko.ts b/src/i18n/locales/ko.ts new file mode 100644 index 0000000..82b5ead --- /dev/null +++ b/src/i18n/locales/ko.ts @@ -0,0 +1,113 @@ +const ko = { + lang: '한국어', + + home_tagline: '대화가 만나는 곳, 서버 없이.', + home_description: + '몇 초 만에 비공개 화상 회의실을 만드세요. 순수 P2P WebRTC — 계정도, 서버도, 중개자도 없습니다. 당신과 사람들, 그리고 여섯 글자 코드만 있으면 됩니다.', + home_your_name: '이름', + home_host: '새 회의 시작', + home_or_join: '또는 참여', + home_meeting_code: '회의 코드', + home_meeting_code_placeholder: 'ABCXYZ', + home_join: '참여', + home_name_required_hint: '주최하거나 참여하려면 이름을 입력하세요.', + home_error_name: '이름을 입력해 주세요.', + home_error_code: '회의 코드는 6글자여야 합니다.', + home_footnote: 'WebRTC를 통한 P2P. 계정도 서버도 없습니다.', + + footer_author: '제작자', + footer_github: 'GitHub', + footer_feedback: '피드백', + + meeting_invalid_code: '잘못된 회의 코드', + meeting_back_home: '홈으로 돌아가기', + meeting_join_title: '회의 참여', + meeting_enter_name: '계속하려면 이름을 입력해 주세요.', + meeting_your_name: '이름', + meeting_join: '참여', + + meeting_preparing: '카메라와 마이크를 준비하는 중…', + meeting_starting: '회의를 시작하는 중…', + meeting_joining: '회의에 참여하는 중…', + + meeting_error_title: '회의에 참여할 수 없습니다', + meeting_error_default: '오류가 발생했습니다.', + meeting_error_unavailable_id: + '이미 사용 중인 회의 코드입니다. 다른 코드를 사용해 보세요.', + meeting_error_peer_unavailable: '회의를 찾을 수 없습니다.', + meeting_error_start: '회의를 시작하지 못했습니다.', + + meeting_ended_host: '호스트가 회의를 종료했습니다', + meeting_ended_self: '회의에서 나갔습니다', + meeting_ended_kicked: '호스트에 의해 회의에서 내보내졌습니다', + + meeting_share_invite_aria: '초대 공유', + meeting_person: '명', + meeting_people: '명', + + meeting_end_for_everyone: '모두에 대해 회의를 종료할까요?', + meeting_leave_title: '회의에서 나갈까요?', + meeting_end_for_everyone_body: + '당신은 호스트입니다. 회의를 종료하면 모두 연결이 끊깁니다.', + meeting_leave_body: '이 회의에서 연결이 끊깁니다.', + meeting_cancel: '취소', + meeting_end: '회의 종료', + meeting_leave: '나가기', + + chat_title: '채팅', + chat_close: '채팅 닫기', + chat_send: '메시지 보내기', + chat_emoji: '이모지 삽입', + chat_placeholder: '메시지…', + chat_empty: '아직 메시지가 없습니다.', + chat_you: '나', + chat_system_joined: (name: string) => `${name}님이 참여했습니다`, + chat_system_left: (name: string) => `${name}님이 나갔습니다`, + + controls_mute: '음소거', + controls_unmute: '음소거 해제', + controls_stop_video: '비디오 중지', + controls_start_video: '비디오 시작', + controls_chat: '채팅', + controls_share: '초대 공유', + controls_participants: '참가자', + controls_leave: '나가기', + + participants_title: '참가자', + participants_close: '참가자 닫기', + participants_host: '호스트', + participants_you: '나', + participants_kick: '회의에서 내보내기', + participants_kick_confirm_title: '참가자를 내보낼까요?', + participants_kick_confirm_body: (name: string) => + `${name}님이 이 회의에서 내보내집니다.`, + participants_kick_confirm: '내보내기', + participants_system_kicked: (name: string) => `${name}님이 내보내졌습니다`, + + rail_resize: '패널 크기 조정', + + share_title: '다른 사람 초대', + share_meeting_code: '회의 코드', + share_copy_code: '코드 복사', + share_copy_link: '링크 복사', + share_copied: '복사됨', + share_invite_link: '초대 링크', + share_via: '공유 방법', + share_more: '더 보기…', + share_more_aria: '추가 공유 옵션', + share_done: '완료', + share_on: (network: string) => `${network}에 공유`, + share_subject: '내 회의에 참여하세요', + share_text: (code: string) => `내 회의에 참여하세요 (코드: ${code})`, + + tile_you: '(나)', + tile_host: '호스트', + + theme_switch_to: (mode: string) => `${mode} 모드로 전환`, + theme_dark: '다크', + theme_light: '라이트', + + language_change: '언어 변경', +} as const; + +export default ko; diff --git a/src/i18n/locales/pt.ts b/src/i18n/locales/pt.ts new file mode 100644 index 0000000..cbe03f3 --- /dev/null +++ b/src/i18n/locales/pt.ts @@ -0,0 +1,113 @@ +const pt = { + lang: 'Português', + + home_tagline: 'Onde as conversas se encontram, sem servidores.', + home_description: + 'Crie uma sala de vídeo privada em segundos. WebRTC puro ponto a ponto — sem contas, sem servidores, sem intermediários. Só você, as suas pessoas e um código de seis letras.', + home_your_name: 'Seu nome', + home_host: 'Iniciar nova reunião', + home_or_join: 'ou entrar', + home_meeting_code: 'Código da reunião', + home_meeting_code_placeholder: 'ABCXYZ', + home_join: 'Entrar', + home_name_required_hint: 'Digite seu nome para hospedar ou entrar.', + home_error_name: 'Por favor, digite seu nome.', + home_error_code: 'O código da reunião deve ter 6 letras.', + home_footnote: 'Ponto a ponto via WebRTC. Sem contas, sem servidores.', + + footer_author: 'Autor', + footer_github: 'GitHub', + footer_feedback: 'Feedback', + + meeting_invalid_code: 'Código de reunião inválido', + meeting_back_home: 'Voltar ao início', + meeting_join_title: 'Entrar na reunião', + meeting_enter_name: 'Por favor, digite seu nome para continuar.', + meeting_your_name: 'Seu nome', + meeting_join: 'Entrar', + + meeting_preparing: 'Preparando sua câmera e microfone…', + meeting_starting: 'Iniciando a reunião…', + meeting_joining: 'Entrando na reunião…', + + meeting_error_title: 'Não foi possível entrar na reunião', + meeting_error_default: 'Ocorreu um erro.', + meeting_error_unavailable_id: + 'Este código de reunião já está em uso. Tente outro.', + meeting_error_peer_unavailable: 'Reunião não encontrada.', + meeting_error_start: 'Falha ao iniciar a reunião.', + + meeting_ended_host: 'O anfitrião encerrou a reunião', + meeting_ended_self: 'Você saiu da reunião', + meeting_ended_kicked: 'Você foi removido da reunião pelo anfitrião', + + meeting_share_invite_aria: 'Compartilhar convite', + meeting_person: 'pessoa', + meeting_people: 'pessoas', + + meeting_end_for_everyone: 'Encerrar a reunião para todos?', + meeting_leave_title: 'Sair da reunião?', + meeting_end_for_everyone_body: + 'Você é o anfitrião. Encerrar a reunião desconectará todos.', + meeting_leave_body: 'Você será desconectado desta reunião.', + meeting_cancel: 'Cancelar', + meeting_end: 'Encerrar reunião', + meeting_leave: 'Sair', + + chat_title: 'Bate-papo', + chat_close: 'Fechar bate-papo', + chat_send: 'Enviar mensagem', + chat_emoji: 'Inserir emoji', + chat_placeholder: 'Mensagem…', + chat_empty: 'Ainda não há mensagens.', + chat_you: 'Você', + chat_system_joined: (name: string) => `${name} entrou`, + chat_system_left: (name: string) => `${name} saiu`, + + controls_mute: 'Silenciar', + controls_unmute: 'Ativar som', + controls_stop_video: 'Parar vídeo', + controls_start_video: 'Iniciar vídeo', + controls_chat: 'Bate-papo', + controls_share: 'Compartilhar convite', + controls_participants: 'Participantes', + controls_leave: 'Sair', + + participants_title: 'Participantes', + participants_close: 'Fechar participantes', + participants_host: 'Anfitrião', + participants_you: 'Você', + participants_kick: 'Remover da reunião', + participants_kick_confirm_title: 'Remover participante?', + participants_kick_confirm_body: (name: string) => + `${name} será removido desta reunião.`, + participants_kick_confirm: 'Remover', + participants_system_kicked: (name: string) => `${name} foi removido`, + + rail_resize: 'Redimensionar painéis', + + share_title: 'Convidar outras pessoas', + share_meeting_code: 'Código da reunião', + share_copy_code: 'Copiar código', + share_copy_link: 'Copiar link', + share_copied: 'Copiado', + share_invite_link: 'Link de convite', + share_via: 'Compartilhar via', + share_more: 'Mais…', + share_more_aria: 'Mais opções de compartilhamento', + share_done: 'Concluído', + share_on: (network: string) => `Compartilhar no ${network}`, + share_subject: 'Entre na minha reunião', + share_text: (code: string) => `Entre na minha reunião (código: ${code})`, + + tile_you: '(você)', + tile_host: 'Anfitrião', + + theme_switch_to: (mode: string) => `Mudar para o modo ${mode}`, + theme_dark: 'escuro', + theme_light: 'claro', + + language_change: 'Alterar idioma', +} as const; + +export default pt; diff --git a/src/i18n/locales/ru.ts b/src/i18n/locales/ru.ts new file mode 100644 index 0000000..63ccfa5 --- /dev/null +++ b/src/i18n/locales/ru.ts @@ -0,0 +1,115 @@ +const ru = { + lang: 'Русский', + + home_tagline: 'Место встречи разговоров — без серверов.', + home_description: + 'Создайте приватную видеокомнату за секунды. Чистый P2P WebRTC — без аккаунтов, серверов и посредников. Только вы, ваши люди и код из шести букв.', + home_your_name: 'Ваше имя', + home_host: 'Создать встречу', + home_or_join: 'или присоединиться', + home_meeting_code: 'Код встречи', + home_meeting_code_placeholder: 'ABCXYZ', + home_join: 'Войти', + home_name_required_hint: + 'Введите имя, чтобы создать встречу или присоединиться.', + home_error_name: 'Пожалуйста, введите ваше имя.', + home_error_code: 'Код встречи должен состоять из 6 букв.', + home_footnote: 'P2P через WebRTC. Без аккаунтов и серверов.', + + footer_author: 'Автор', + footer_github: 'GitHub', + footer_feedback: 'Обратная связь', + + meeting_invalid_code: 'Неверный код встречи', + meeting_back_home: 'На главную', + meeting_join_title: 'Присоединиться к встрече', + meeting_enter_name: 'Пожалуйста, введите имя, чтобы продолжить.', + meeting_your_name: 'Ваше имя', + meeting_join: 'Войти', + + meeting_preparing: 'Подготовка камеры и микрофона…', + meeting_starting: 'Запуск встречи…', + meeting_joining: 'Подключение к встрече…', + + meeting_error_title: 'Не удалось присоединиться к встрече', + meeting_error_default: 'Произошла ошибка.', + meeting_error_unavailable_id: + 'Этот код встречи уже используется. Попробуйте другой.', + meeting_error_peer_unavailable: 'Встреча не найдена.', + meeting_error_start: 'Не удалось запустить встречу.', + + meeting_ended_host: 'Организатор завершил встречу', + meeting_ended_self: 'Вы покинули встречу', + meeting_ended_kicked: 'Организатор удалил вас из встречи', + + meeting_share_invite_aria: 'Поделиться приглашением', + meeting_person: 'участник', + meeting_people: 'участников', + + meeting_end_for_everyone: 'Завершить встречу для всех?', + meeting_leave_title: 'Покинуть встречу?', + meeting_end_for_everyone_body: + 'Вы организатор. Завершение встречи отключит всех участников.', + meeting_leave_body: 'Вы будете отключены от этой встречи.', + meeting_cancel: 'Отмена', + meeting_end: 'Завершить встречу', + meeting_leave: 'Выйти', + + chat_title: 'Чат', + chat_close: 'Закрыть чат', + chat_send: 'Отправить сообщение', + chat_emoji: 'Вставить эмодзи', + chat_placeholder: 'Сообщение…', + chat_empty: 'Сообщений пока нет.', + chat_you: 'Вы', + chat_system_joined: (name: string) => `${name} присоединился(ась)`, + chat_system_left: (name: string) => `${name} вышел(ла)`, + + controls_mute: 'Выключить микрофон', + controls_unmute: 'Включить микрофон', + controls_stop_video: 'Выключить видео', + controls_start_video: 'Включить видео', + controls_chat: 'Чат', + controls_share: 'Поделиться приглашением', + controls_participants: 'Участники', + controls_leave: 'Выйти', + + participants_title: 'Участники', + participants_close: 'Закрыть участников', + participants_host: 'Организатор', + participants_you: 'Вы', + participants_kick: 'Удалить из встречи', + participants_kick_confirm_title: 'Удалить участника?', + participants_kick_confirm_body: (name: string) => + `${name} будет удалён из этой встречи.`, + participants_kick_confirm: 'Удалить', + participants_system_kicked: (name: string) => `${name} удалён`, + + rail_resize: 'Изменить размер панелей', + + share_title: 'Пригласить других', + share_meeting_code: 'Код встречи', + share_copy_code: 'Копировать код', + share_copy_link: 'Копировать ссылку', + share_copied: 'Скопировано', + share_invite_link: 'Ссылка-приглашение', + share_via: 'Поделиться через', + share_more: 'Ещё…', + share_more_aria: 'Дополнительные варианты', + share_done: 'Готово', + share_on: (network: string) => `Поделиться в ${network}`, + share_subject: 'Присоединяйтесь к моей встрече', + share_text: (code: string) => + `Присоединяйтесь к моей встрече (код: ${code})`, + + tile_you: '(вы)', + tile_host: 'Организатор', + + theme_switch_to: (mode: string) => `Переключить на ${mode} тему`, + theme_dark: 'тёмную', + theme_light: 'светлую', + + language_change: 'Изменить язык', +} as const; + +export default ru; diff --git a/src/i18n/translations.ts b/src/i18n/translations.ts index b74c2d3..151c908 100644 --- a/src/i18n/translations.ts +++ b/src/i18n/translations.ts @@ -8,6 +8,10 @@ import zh from './locales/zh'; import fr from './locales/fr'; import es from './locales/es'; import ja from './locales/ja'; +import de from './locales/de'; +import pt from './locales/pt'; +import ko from './locales/ko'; +import ru from './locales/ru'; import { getLanguagePreference } from './LocalLanguagePreference'; const TranslationsPerLang: Record = { @@ -16,6 +20,10 @@ const TranslationsPerLang: Record = { fr, es, ja, + de, + pt, + ko, + ru, }; export function detectDefaultLocale(): SupportedLanguages { diff --git a/src/i18n/translations.type.test.ts b/src/i18n/translations.type.test.ts index 55bf6c3..49aee28 100644 --- a/src/i18n/translations.type.test.ts +++ b/src/i18n/translations.type.test.ts @@ -6,7 +6,7 @@ describe('supportLanguage', () => { }); it('returns undefined for unsupported codes', () => { - expect(supportLanguage('de')).toBeUndefined(); + expect(supportLanguage('it')).toBeUndefined(); expect(supportLanguage('')).toBeUndefined(); expect(supportLanguage('EN')).toBeUndefined(); }); diff --git a/src/i18n/translations.type.ts b/src/i18n/translations.type.ts index 59c517b..58dd6c4 100644 --- a/src/i18n/translations.type.ts +++ b/src/i18n/translations.type.ts @@ -1,6 +1,16 @@ import en from './locales/en'; -export const SUPPORTED_LANGUAGES = ['en', 'zh', 'fr', 'es', 'ja'] as const; +export const SUPPORTED_LANGUAGES = [ + 'en', + 'zh', + 'fr', + 'es', + 'ja', + 'de', + 'pt', + 'ko', + 'ru', +] as const; export type SupportedLanguages = typeof SUPPORTED_LANGUAGES[number];