All checks were successful
Deploy tt-tagebuch / deploy (push) Successful in 44s
- Added `ACCESS_NETWORK_STATE` permission in AndroidManifest.xml to monitor network connectivity. - Introduced `NetworkConnectivityHolder` to manage network state and trigger data refresh when connectivity is restored. - Updated `DiaryManager` and `MembersManager` to support offline caching, allowing users to view previously loaded data when offline. - Enhanced localization by adding new keys for offline cache hints in both German and English, improving user experience during connectivity issues. - Updated UI components to display offline cache messages, ensuring users are informed when data is being served from cache.
624 lines
28 KiB
JavaScript
624 lines
28 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Generates i18n maps for the native mobile app from the web source of truth.
|
||
*
|
||
* Source of truth: `frontend/src/i18n/locales/*.json`
|
||
*
|
||
* Output:
|
||
* - `mobile-app/shared/src/commonMain/kotlin/de/tsschulz/tt_tagebuch/shared/i18n/MobileStrings.kt`
|
||
*
|
||
* Notes:
|
||
* - German (`de`) is the canonical key set.
|
||
* - Missing locale keys fall back to German at generation time so every mobile locale is complete.
|
||
*/
|
||
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const ROOT = path.resolve(__dirname, '..');
|
||
const SOURCE_DIR = path.join(ROOT, 'frontend', 'src', 'i18n', 'locales');
|
||
const OUT = path.join(
|
||
ROOT,
|
||
'mobile-app',
|
||
'shared',
|
||
'src',
|
||
'commonMain',
|
||
'kotlin',
|
||
'de',
|
||
'tsschulz',
|
||
'tt_tagebuch',
|
||
'shared',
|
||
'i18n',
|
||
'MobileStrings.kt'
|
||
);
|
||
|
||
const LOCALE_LABELS = {
|
||
'de': 'Deutsch',
|
||
'de-CH': 'Deutsch (Schweiz)',
|
||
'de-extended': 'Deutsch (erweitert)',
|
||
'en-US': 'English (US)',
|
||
'en-GB': 'English (UK)',
|
||
'en-AU': 'English (AU)',
|
||
'es': 'Español',
|
||
'fr': 'Français',
|
||
'it': 'Italiano',
|
||
'pl': 'Polski',
|
||
'ja': '日本語',
|
||
'th': 'ไทย',
|
||
'tl': 'Tagalog',
|
||
'fil': 'Filipino',
|
||
'zh': '中文',
|
||
};
|
||
|
||
const MOBILE_STRINGS = {
|
||
de: {
|
||
'mobile.add': 'Hinzufügen',
|
||
'mobile.appLoading': 'App wird geladen',
|
||
'mobile.cancel': 'Abbrechen',
|
||
'mobile.clubRequest': 'Zugriff anfragen',
|
||
'mobile.clubRequested': 'Angefragt',
|
||
'mobile.createDiaryEntry': 'Eintrag erstellen',
|
||
'mobile.deleteNote': 'Notiz löschen',
|
||
'mobile.deleteTag': 'Tag entfernen',
|
||
'mobile.editTimes': 'Zeiten bearbeiten',
|
||
'mobile.emailAndPasswordRequired': 'E-Mail und Passwort sind erforderlich',
|
||
'mobile.entry': 'Eintrag',
|
||
'mobile.language': 'Sprache',
|
||
'mobile.lastTraining': 'Letztes Training',
|
||
'mobile.loginInProgress': 'Anmelden...',
|
||
'mobile.loginFailed': 'Login fehlgeschlagen',
|
||
'mobile.more': 'Mehr',
|
||
'mobile.new': 'Neu',
|
||
'mobile.newNote': 'Neue Notiz',
|
||
'mobile.newTag': 'Neuer Tag',
|
||
'mobile.noDiaryEntries': 'Noch keine Tagebuch-Einträge',
|
||
'mobile.noMembers': 'Keine Mitglieder gefunden',
|
||
'mobile.noResults': 'Keine Treffer',
|
||
'mobile.noTimes': 'Keine Zeiten',
|
||
'mobile.offlineCacheHint': 'Keine Verbindung – zuletzt geladene Daten.',
|
||
'mobile.participationTop': 'Top Teilnahmen',
|
||
'mobile.refresh': 'Aktualisieren',
|
||
'mobile.requestedAccess': 'Angefragt',
|
||
'mobile.role': 'Rolle',
|
||
'mobile.saveEntry': 'Speichern',
|
||
'mobile.search': 'Suche',
|
||
'mobile.select': 'Auswählen',
|
||
'mobile.sessionCheck': 'Session prüfen',
|
||
'mobile.sessionCheckFailed': 'Session check fehlgeschlagen',
|
||
'mobile.sessionInvalid': 'Session ungültig',
|
||
'mobile.sessionValid': 'Session gültig',
|
||
'mobile.status': 'Status',
|
||
'mobile.active': 'Aktiv',
|
||
'mobile.inactive': 'Inaktiv',
|
||
'mobile.user': 'Benutzer',
|
||
},
|
||
en: {
|
||
'mobile.add': 'Add',
|
||
'mobile.appLoading': 'Loading app',
|
||
'mobile.cancel': 'Cancel',
|
||
'mobile.clubRequest': 'Request access',
|
||
'mobile.clubRequested': 'Requested',
|
||
'mobile.createDiaryEntry': 'Create entry',
|
||
'mobile.deleteNote': 'Delete note',
|
||
'mobile.deleteTag': 'Remove tag',
|
||
'mobile.editTimes': 'Edit times',
|
||
'mobile.emailAndPasswordRequired': 'Email and password are required',
|
||
'mobile.entry': 'Entry',
|
||
'mobile.language': 'Language',
|
||
'mobile.lastTraining': 'Last training',
|
||
'mobile.loginInProgress': 'Signing in...',
|
||
'mobile.loginFailed': 'Login failed',
|
||
'mobile.more': 'More',
|
||
'mobile.new': 'New',
|
||
'mobile.newNote': 'New note',
|
||
'mobile.newTag': 'New tag',
|
||
'mobile.noDiaryEntries': 'No diary entries yet',
|
||
'mobile.noMembers': 'No members found',
|
||
'mobile.noResults': 'No results',
|
||
'mobile.noTimes': 'No times',
|
||
'mobile.offlineCacheHint': 'No connection – showing last loaded data.',
|
||
'mobile.participationTop': 'Top attendance',
|
||
'mobile.refresh': 'Refresh',
|
||
'mobile.requestedAccess': 'Requested',
|
||
'mobile.role': 'Role',
|
||
'mobile.saveEntry': 'Save',
|
||
'mobile.search': 'Search',
|
||
'mobile.select': 'Select',
|
||
'mobile.sessionCheck': 'Check session',
|
||
'mobile.sessionCheckFailed': 'Session check failed',
|
||
'mobile.sessionInvalid': 'Session invalid',
|
||
'mobile.sessionValid': 'Session valid',
|
||
'mobile.status': 'Status',
|
||
'mobile.active': 'Active',
|
||
'mobile.inactive': 'Inactive',
|
||
'mobile.user': 'User',
|
||
},
|
||
es: {
|
||
'mobile.offlineCacheHint': 'Sin conexión: se muestran los datos cargados por última vez.',
|
||
},
|
||
fr: {
|
||
'mobile.offlineCacheHint': 'Pas de connexion – affichage des données du dernier chargement.',
|
||
},
|
||
it: {
|
||
'mobile.offlineCacheHint': 'Nessuna connessione – dati dell’ultimo caricamento.',
|
||
},
|
||
pl: {
|
||
'mobile.offlineCacheHint': 'Brak połączenia – wyświetlane ostatnio wczytane dane.',
|
||
},
|
||
ja: {
|
||
'mobile.offlineCacheHint': 'ネットワークに接続されていません。最後に読み込んだデータを表示しています。',
|
||
},
|
||
th: {
|
||
'mobile.offlineCacheHint': 'ไม่มีการเชื่อมต่อ – แสดงข้อมูลที่โหลดล่าสุด',
|
||
},
|
||
tl: {
|
||
'mobile.offlineCacheHint': 'Walang koneksyon – ipinapakita ang huling na-load na datos.',
|
||
},
|
||
fil: {
|
||
'mobile.offlineCacheHint': 'Walang koneksyon – ipinapakita ang huling na-load na datos.',
|
||
},
|
||
zh: {
|
||
'mobile.offlineCacheHint': '无网络连接,显示上次加载的数据。',
|
||
},
|
||
};
|
||
|
||
/** Kalender-Tab (CalendarScreen.kt): eigene Übersetzungen pro Locale, damit nicht nur Deutsch aus dem Basis-Flat greift. */
|
||
const MOBILE_CALENDAR_I18N = {
|
||
de: {
|
||
'mobile.calendarSourceDiary': 'Tagebuch',
|
||
'mobile.calendarSourceTrainingTimes': 'Trainingszeiten',
|
||
'mobile.calendarSourceCancellations': 'Trainingsausfälle',
|
||
'mobile.calendarSourceTournaments': 'Turniere',
|
||
'mobile.calendarSourceMatches': 'Punktspiele',
|
||
'mobile.calendarSourceOfficial': 'Turnierteilnahmen',
|
||
'mobile.calendarSourceHolidays': 'Ferien/Feiertage',
|
||
'mobile.calendarLegendTraining': 'Training',
|
||
'mobile.calendarLegendTournament': 'Turnier',
|
||
'mobile.calendarLegendOfficial': 'Teilnahme',
|
||
'mobile.calendarLegendMatch': 'Punktspiel',
|
||
'mobile.calendarLegendHoliday': 'Feiertag',
|
||
'mobile.calendarLegendSchool': 'Ferien',
|
||
'mobile.calendarLegendCancellation': 'Ausfall',
|
||
'mobile.calendarTitle': 'Kalender',
|
||
'mobile.calendarSubtitle': 'Training, Turniere, Spiele und Feiertage im Monat.',
|
||
'mobile.calendarToday': 'Heute',
|
||
'mobile.calendarAllSourcesFailed': 'Kalenderdaten konnten nicht geladen werden.',
|
||
'mobile.calendarSourceFailed': 'nicht geladen',
|
||
'mobile.calendarLegend': 'Anzeige',
|
||
'mobile.calendarCancellationTitle': 'Training fällt aus',
|
||
'mobile.calendarCancellationHint': 'Blendet regelmäßige Trainingszeiten an den Tagen aus.',
|
||
'mobile.calendarCancellationStart': 'Datum (YYYY-MM-DD)',
|
||
'mobile.calendarCancellationEnd': 'Bis optional',
|
||
'mobile.calendarCancellationReason': 'Grund',
|
||
'mobile.calendarCancellationSave': 'Eintragen',
|
||
'mobile.calendarCancellationInMonth': 'Ausfälle diesen Monat',
|
||
'mobile.calendarGridTitle': 'Monat',
|
||
'mobile.calendarAgendaTitle': 'Termine im Monat',
|
||
'mobile.calendarAgendaEmpty': 'Keine Termine in diesem Monat.',
|
||
},
|
||
en: {
|
||
'mobile.calendarSourceDiary': 'Diary',
|
||
'mobile.calendarSourceTrainingTimes': 'Training times',
|
||
'mobile.calendarSourceCancellations': 'Training cancellations',
|
||
'mobile.calendarSourceTournaments': 'Tournaments',
|
||
'mobile.calendarSourceMatches': 'League matches',
|
||
'mobile.calendarSourceOfficial': 'Official participations',
|
||
'mobile.calendarSourceHolidays': 'Holidays / school breaks',
|
||
'mobile.calendarLegendTraining': 'Training',
|
||
'mobile.calendarLegendTournament': 'Tournament',
|
||
'mobile.calendarLegendOfficial': 'Participation',
|
||
'mobile.calendarLegendMatch': 'League match',
|
||
'mobile.calendarLegendHoliday': 'Public holiday',
|
||
'mobile.calendarLegendSchool': 'School break',
|
||
'mobile.calendarLegendCancellation': 'Cancellation',
|
||
'mobile.calendarTitle': 'Calendar',
|
||
'mobile.calendarSubtitle': 'Training, tournaments, matches and holidays for the month.',
|
||
'mobile.calendarToday': 'Today',
|
||
'mobile.calendarAllSourcesFailed': 'Calendar data could not be loaded.',
|
||
'mobile.calendarSourceFailed': 'not loaded',
|
||
'mobile.calendarLegend': 'Display',
|
||
'mobile.calendarCancellationTitle': 'Training cancelled',
|
||
'mobile.calendarCancellationHint': 'Hides recurring training times on those days.',
|
||
'mobile.calendarCancellationStart': 'Date (YYYY-MM-DD)',
|
||
'mobile.calendarCancellationEnd': 'Until (optional)',
|
||
'mobile.calendarCancellationReason': 'Reason',
|
||
'mobile.calendarCancellationSave': 'Save',
|
||
'mobile.calendarCancellationInMonth': 'Cancellations this month',
|
||
'mobile.calendarGridTitle': 'Month',
|
||
'mobile.calendarAgendaTitle': 'Events this month',
|
||
'mobile.calendarAgendaEmpty': 'No events in this month.',
|
||
},
|
||
es: {
|
||
'mobile.calendarSourceDiary': 'Diario',
|
||
'mobile.calendarSourceTrainingTimes': 'Horarios de entrenamiento',
|
||
'mobile.calendarSourceCancellations': 'Cancelaciones',
|
||
'mobile.calendarSourceTournaments': 'Torneos',
|
||
'mobile.calendarSourceMatches': 'Partidos de liga',
|
||
'mobile.calendarSourceOfficial': 'Participaciones oficiales',
|
||
'mobile.calendarSourceHolidays': 'Vacaciones / festivos',
|
||
'mobile.calendarLegendTraining': 'Entrenamiento',
|
||
'mobile.calendarLegendTournament': 'Torneo',
|
||
'mobile.calendarLegendOfficial': 'Participación',
|
||
'mobile.calendarLegendMatch': 'Partido de liga',
|
||
'mobile.calendarLegendHoliday': 'Festivo',
|
||
'mobile.calendarLegendSchool': 'Vacaciones escolares',
|
||
'mobile.calendarLegendCancellation': 'Cancelación',
|
||
'mobile.calendarTitle': 'Calendario',
|
||
'mobile.calendarSubtitle': 'Entrenamientos, torneos, partidos y festivos del mes.',
|
||
'mobile.calendarToday': 'Hoy',
|
||
'mobile.calendarAllSourcesFailed': 'No se pudieron cargar los datos del calendario.',
|
||
'mobile.calendarSourceFailed': 'no cargado',
|
||
'mobile.calendarLegend': 'Vista',
|
||
'mobile.calendarCancellationTitle': 'Entrenamiento cancelado',
|
||
'mobile.calendarCancellationHint': 'Oculta los horarios recurrentes en esos días.',
|
||
'mobile.calendarCancellationStart': 'Fecha (AAAA-MM-DD)',
|
||
'mobile.calendarCancellationEnd': 'Hasta (opcional)',
|
||
'mobile.calendarCancellationReason': 'Motivo',
|
||
'mobile.calendarCancellationSave': 'Guardar',
|
||
'mobile.calendarCancellationInMonth': 'Cancelaciones este mes',
|
||
'mobile.calendarGridTitle': 'Mes',
|
||
'mobile.calendarAgendaTitle': 'Citas del mes',
|
||
'mobile.calendarAgendaEmpty': 'No hay citas en este mes.',
|
||
},
|
||
fr: {
|
||
'mobile.calendarSourceDiary': 'Journal',
|
||
'mobile.calendarSourceTrainingTimes': 'Créneaux d’entraînement',
|
||
'mobile.calendarSourceCancellations': 'Annulations',
|
||
'mobile.calendarSourceTournaments': 'Tournois',
|
||
'mobile.calendarSourceMatches': 'Matchs de championnat',
|
||
'mobile.calendarSourceOfficial': 'Participations officielles',
|
||
'mobile.calendarSourceHolidays': 'Vacances / jours fériés',
|
||
'mobile.calendarLegendTraining': 'Entraînement',
|
||
'mobile.calendarLegendTournament': 'Tournoi',
|
||
'mobile.calendarLegendOfficial': 'Participation',
|
||
'mobile.calendarLegendMatch': 'Match',
|
||
'mobile.calendarLegendHoliday': 'Jour férié',
|
||
'mobile.calendarLegendSchool': 'Vacances scolaires',
|
||
'mobile.calendarLegendCancellation': 'Annulation',
|
||
'mobile.calendarTitle': 'Calendrier',
|
||
'mobile.calendarSubtitle': 'Entraînements, tournois, matchs et vacances du mois.',
|
||
'mobile.calendarToday': 'Aujourd’hui',
|
||
'mobile.calendarAllSourcesFailed': 'Impossible de charger le calendrier.',
|
||
'mobile.calendarSourceFailed': 'non chargé',
|
||
'mobile.calendarLegend': 'Affichage',
|
||
'mobile.calendarCancellationTitle': 'Entraînement annulé',
|
||
'mobile.calendarCancellationHint': 'Masque les créneaux récurrents ces jours-là.',
|
||
'mobile.calendarCancellationStart': 'Date (AAAA-MM-JJ)',
|
||
'mobile.calendarCancellationEnd': 'Jusqu’au (optionnel)',
|
||
'mobile.calendarCancellationReason': 'Motif',
|
||
'mobile.calendarCancellationSave': 'Enregistrer',
|
||
'mobile.calendarCancellationInMonth': 'Annulations ce mois-ci',
|
||
'mobile.calendarGridTitle': 'Mois',
|
||
'mobile.calendarAgendaTitle': 'Rendez-vous du mois',
|
||
'mobile.calendarAgendaEmpty': 'Aucun rendez-vous ce mois-ci.',
|
||
},
|
||
it: {
|
||
'mobile.calendarSourceDiary': 'Diario',
|
||
'mobile.calendarSourceTrainingTimes': 'Orari allenamento',
|
||
'mobile.calendarSourceCancellations': 'Cancellazioni',
|
||
'mobile.calendarSourceTournaments': 'Tornei',
|
||
'mobile.calendarSourceMatches': 'Partite campionato',
|
||
'mobile.calendarSourceOfficial': 'Partecipazioni ufficiali',
|
||
'mobile.calendarSourceHolidays': 'Vacanze / festività',
|
||
'mobile.calendarLegendTraining': 'Allenamento',
|
||
'mobile.calendarLegendTournament': 'Torneo',
|
||
'mobile.calendarLegendOfficial': 'Partecipazione',
|
||
'mobile.calendarLegendMatch': 'Partita',
|
||
'mobile.calendarLegendHoliday': 'Festività',
|
||
'mobile.calendarLegendSchool': 'Vacanze scolastiche',
|
||
'mobile.calendarLegendCancellation': 'Cancellazione',
|
||
'mobile.calendarTitle': 'Calendario',
|
||
'mobile.calendarSubtitle': 'Allenamenti, tornei, partite e festività del mese.',
|
||
'mobile.calendarToday': 'Oggi',
|
||
'mobile.calendarAllSourcesFailed': 'Impossibile caricare il calendario.',
|
||
'mobile.calendarSourceFailed': 'non caricato',
|
||
'mobile.calendarLegend': 'Vista',
|
||
'mobile.calendarCancellationTitle': 'Allenamento annullato',
|
||
'mobile.calendarCancellationHint': 'Nasconde gli orari ricorrenti in quei giorni.',
|
||
'mobile.calendarCancellationStart': 'Data (AAAA-MM-GG)',
|
||
'mobile.calendarCancellationEnd': 'Fino al (opzionale)',
|
||
'mobile.calendarCancellationReason': 'Motivo',
|
||
'mobile.calendarCancellationSave': 'Salva',
|
||
'mobile.calendarCancellationInMonth': 'Cancellazioni questo mese',
|
||
'mobile.calendarGridTitle': 'Mese',
|
||
'mobile.calendarAgendaTitle': 'Appuntamenti del mese',
|
||
'mobile.calendarAgendaEmpty': 'Nessun appuntamento in questo mese.',
|
||
},
|
||
pl: {
|
||
'mobile.calendarSourceDiary': 'Dziennik',
|
||
'mobile.calendarSourceTrainingTimes': 'Terminy treningów',
|
||
'mobile.calendarSourceCancellations': 'Odwołania',
|
||
'mobile.calendarSourceTournaments': 'Turnieje',
|
||
'mobile.calendarSourceMatches': 'Mecze ligowe',
|
||
'mobile.calendarSourceOfficial': 'Oficjalne starty',
|
||
'mobile.calendarSourceHolidays': 'Święta / ferie',
|
||
'mobile.calendarLegendTraining': 'Trening',
|
||
'mobile.calendarLegendTournament': 'Turniej',
|
||
'mobile.calendarLegendOfficial': 'Start',
|
||
'mobile.calendarLegendMatch': 'Mecz',
|
||
'mobile.calendarLegendHoliday': 'Święto',
|
||
'mobile.calendarLegendSchool': 'Ferie',
|
||
'mobile.calendarLegendCancellation': 'Odwołanie',
|
||
'mobile.calendarTitle': 'Kalendarz',
|
||
'mobile.calendarSubtitle': 'Treningi, turnieje, mecze i święta w miesiącu.',
|
||
'mobile.calendarToday': 'Dziś',
|
||
'mobile.calendarAllSourcesFailed': 'Nie udało się wczytać kalendarza.',
|
||
'mobile.calendarSourceFailed': 'nie wczytano',
|
||
'mobile.calendarLegend': 'Widok',
|
||
'mobile.calendarCancellationTitle': 'Trening odwołany',
|
||
'mobile.calendarCancellationHint': 'Ukrywa cykliczne terminy treningów w te dni.',
|
||
'mobile.calendarCancellationStart': 'Data (RRRR-MM-DD)',
|
||
'mobile.calendarCancellationEnd': 'Do (opcjonalnie)',
|
||
'mobile.calendarCancellationReason': 'Powód',
|
||
'mobile.calendarCancellationSave': 'Zapisz',
|
||
'mobile.calendarCancellationInMonth': 'Odwołania w tym miesiącu',
|
||
'mobile.calendarGridTitle': 'Miesiąc',
|
||
'mobile.calendarAgendaTitle': 'Terminy w miesiącu',
|
||
'mobile.calendarAgendaEmpty': 'Brak terminów w tym miesiącu.',
|
||
},
|
||
ja: {
|
||
'mobile.calendarSourceDiary': '日記',
|
||
'mobile.calendarSourceTrainingTimes': '練習時間',
|
||
'mobile.calendarSourceCancellations': '練習中止',
|
||
'mobile.calendarSourceTournaments': '大会',
|
||
'mobile.calendarSourceMatches': 'リーグ戦',
|
||
'mobile.calendarSourceOfficial': '公式参加',
|
||
'mobile.calendarSourceHolidays': '休日・学校休暇',
|
||
'mobile.calendarLegendTraining': '練習',
|
||
'mobile.calendarLegendTournament': '大会',
|
||
'mobile.calendarLegendOfficial': '参加',
|
||
'mobile.calendarLegendMatch': 'リーグ戦',
|
||
'mobile.calendarLegendHoliday': '祝日',
|
||
'mobile.calendarLegendSchool': '学校の休み',
|
||
'mobile.calendarLegendCancellation': '中止',
|
||
'mobile.calendarTitle': 'カレンダー',
|
||
'mobile.calendarSubtitle': '今月の練習・大会・試合・休日。',
|
||
'mobile.calendarToday': '今日',
|
||
'mobile.calendarAllSourcesFailed': 'カレンダーを読み込めませんでした。',
|
||
'mobile.calendarSourceFailed': '未読込',
|
||
'mobile.calendarLegend': '表示',
|
||
'mobile.calendarCancellationTitle': '練習中止',
|
||
'mobile.calendarCancellationHint': 'その日の定例練習時間を非表示にします。',
|
||
'mobile.calendarCancellationStart': '日付 (YYYY-MM-DD)',
|
||
'mobile.calendarCancellationEnd': '終了日(任意)',
|
||
'mobile.calendarCancellationReason': '理由',
|
||
'mobile.calendarCancellationSave': '保存',
|
||
'mobile.calendarCancellationInMonth': '今月の中止',
|
||
'mobile.calendarGridTitle': '月',
|
||
'mobile.calendarAgendaTitle': '今月の予定',
|
||
'mobile.calendarAgendaEmpty': '今月の予定はありません。',
|
||
},
|
||
zh: {
|
||
'mobile.calendarSourceDiary': '日记',
|
||
'mobile.calendarSourceTrainingTimes': '训练时间',
|
||
'mobile.calendarSourceCancellations': '训练取消',
|
||
'mobile.calendarSourceTournaments': '比赛',
|
||
'mobile.calendarSourceMatches': '联赛',
|
||
'mobile.calendarSourceOfficial': '官方参赛',
|
||
'mobile.calendarSourceHolidays': '假期/节日',
|
||
'mobile.calendarLegendTraining': '训练',
|
||
'mobile.calendarLegendTournament': '锦标赛',
|
||
'mobile.calendarLegendOfficial': '参赛',
|
||
'mobile.calendarLegendMatch': '联赛',
|
||
'mobile.calendarLegendHoliday': '节日',
|
||
'mobile.calendarLegendSchool': '学校假期',
|
||
'mobile.calendarLegendCancellation': '取消',
|
||
'mobile.calendarTitle': '日历',
|
||
'mobile.calendarSubtitle': '本月训练、比赛与假期一览。',
|
||
'mobile.calendarToday': '今天',
|
||
'mobile.calendarAllSourcesFailed': '无法加载日历数据。',
|
||
'mobile.calendarSourceFailed': '未加载',
|
||
'mobile.calendarLegend': '显示',
|
||
'mobile.calendarCancellationTitle': '训练取消',
|
||
'mobile.calendarCancellationHint': '在这些日期隐藏定期训练时间。',
|
||
'mobile.calendarCancellationStart': '日期 (YYYY-MM-DD)',
|
||
'mobile.calendarCancellationEnd': '结束(可选)',
|
||
'mobile.calendarCancellationReason': '原因',
|
||
'mobile.calendarCancellationSave': '保存',
|
||
'mobile.calendarCancellationInMonth': '本月取消',
|
||
'mobile.calendarGridTitle': '月份',
|
||
'mobile.calendarAgendaTitle': '本月日程',
|
||
'mobile.calendarAgendaEmpty': '本月没有日程。',
|
||
},
|
||
th: {
|
||
'mobile.calendarSourceDiary': 'ไดอารี่',
|
||
'mobile.calendarSourceTrainingTimes': 'เวลาฝึก',
|
||
'mobile.calendarSourceCancellations': 'ยกเลิกการฝึก',
|
||
'mobile.calendarSourceTournaments': 'การแข่งขัน',
|
||
'mobile.calendarSourceMatches': 'แมตช์ลีก',
|
||
'mobile.calendarSourceOfficial': 'การเข้าร่วมอย่างเป็นทางการ',
|
||
'mobile.calendarSourceHolidays': 'วันหยุด/ปิดเทอม',
|
||
'mobile.calendarLegendTraining': 'ฝึก',
|
||
'mobile.calendarLegendTournament': 'ทัวร์นาเมนต์',
|
||
'mobile.calendarLegendOfficial': 'เข้าร่วม',
|
||
'mobile.calendarLegendMatch': 'แมตช์',
|
||
'mobile.calendarLegendHoliday': 'วันหยุด',
|
||
'mobile.calendarLegendSchool': 'ปิดเทอม',
|
||
'mobile.calendarLegendCancellation': 'ยกเลิก',
|
||
'mobile.calendarTitle': 'ปฏิทิน',
|
||
'mobile.calendarSubtitle': 'การฝึก ทัวร์นาเมนต์ แมตช์ และวันหยุดในเดือนนี้',
|
||
'mobile.calendarToday': 'วันนี้',
|
||
'mobile.calendarAllSourcesFailed': 'โหลดข้อมูลปฏิทินไม่สำเร็จ',
|
||
'mobile.calendarSourceFailed': 'ไม่ได้โหลด',
|
||
'mobile.calendarLegend': 'แสดง',
|
||
'mobile.calendarCancellationTitle': 'ยกเลิกการฝึก',
|
||
'mobile.calendarCancellationHint': 'ซ่อนเวลาฝึกประจำในวันเหล่านั้น',
|
||
'mobile.calendarCancellationStart': 'วันที่ (YYYY-MM-DD)',
|
||
'mobile.calendarCancellationEnd': 'ถึง (ไม่บังคับ)',
|
||
'mobile.calendarCancellationReason': 'เหตุผล',
|
||
'mobile.calendarCancellationSave': 'บันทึก',
|
||
'mobile.calendarCancellationInMonth': 'การยกเลิกในเดือนนี้',
|
||
'mobile.calendarGridTitle': 'เดือน',
|
||
'mobile.calendarAgendaTitle': 'นัดในเดือนนี้',
|
||
'mobile.calendarAgendaEmpty': 'ไม่มีนัดในเดือนนี้',
|
||
},
|
||
tl: {
|
||
'mobile.calendarSourceDiary': 'Diaryo',
|
||
'mobile.calendarSourceTrainingTimes': 'Mga oras ng training',
|
||
'mobile.calendarSourceCancellations': 'Mga pagkansela',
|
||
'mobile.calendarSourceTournaments': 'Mga torneo',
|
||
'mobile.calendarSourceMatches': 'Mga laban sa liga',
|
||
'mobile.calendarSourceOfficial': 'Opisyal na pakikilahok',
|
||
'mobile.calendarSourceHolidays': 'Holiday / bakasyon',
|
||
'mobile.calendarLegendTraining': 'Training',
|
||
'mobile.calendarLegendTournament': 'Torneo',
|
||
'mobile.calendarLegendOfficial': 'Pakikilahok',
|
||
'mobile.calendarLegendMatch': 'Laban',
|
||
'mobile.calendarLegendHoliday': 'Holiday',
|
||
'mobile.calendarLegendSchool': 'Bakasyon sa paaralan',
|
||
'mobile.calendarLegendCancellation': 'Kansela',
|
||
'mobile.calendarTitle': 'Kalendaryo',
|
||
'mobile.calendarSubtitle': 'Training, torneo, liga at holiday sa buwan.',
|
||
'mobile.calendarToday': 'Ngayon',
|
||
'mobile.calendarAllSourcesFailed': 'Hindi ma-load ang kalendaryo.',
|
||
'mobile.calendarSourceFailed': 'hindi na-load',
|
||
'mobile.calendarLegend': 'Ipakita',
|
||
'mobile.calendarCancellationTitle': 'Kanseladong training',
|
||
'mobile.calendarCancellationHint': 'Itinatago ang paulit-ulit na oras ng training sa mga araw na iyon.',
|
||
'mobile.calendarCancellationStart': 'Petsa (YYYY-MM-DD)',
|
||
'mobile.calendarCancellationEnd': 'Hanggang (opsyonal)',
|
||
'mobile.calendarCancellationReason': 'Dahilan',
|
||
'mobile.calendarCancellationSave': 'I-save',
|
||
'mobile.calendarCancellationInMonth': 'Mga kansela ngayong buwan',
|
||
'mobile.calendarGridTitle': 'Buwan',
|
||
'mobile.calendarAgendaTitle': 'Mga appointment sa buwan',
|
||
'mobile.calendarAgendaEmpty': 'Walang appointment sa buwang ito.',
|
||
},
|
||
fil: {
|
||
'mobile.calendarSourceDiary': 'Tala-arawan',
|
||
'mobile.calendarSourceTrainingTimes': 'Mga oras ng pagsasanay',
|
||
'mobile.calendarSourceCancellations': 'Mga pagkansela',
|
||
'mobile.calendarSourceTournaments': 'Mga torneo',
|
||
'mobile.calendarSourceMatches': 'Mga laban sa liga',
|
||
'mobile.calendarSourceOfficial': 'Opisyal na pakikilahok',
|
||
'mobile.calendarSourceHolidays': 'Holiday / bakasyon',
|
||
'mobile.calendarLegendTraining': 'Pagsasanay',
|
||
'mobile.calendarLegendTournament': 'Torneo',
|
||
'mobile.calendarLegendOfficial': 'Pakikilahok',
|
||
'mobile.calendarLegendMatch': 'Laban',
|
||
'mobile.calendarLegendHoliday': 'Holiday',
|
||
'mobile.calendarLegendSchool': 'Bakasyon sa paaralan',
|
||
'mobile.calendarLegendCancellation': 'Kansela',
|
||
'mobile.calendarTitle': 'Kalendaryo',
|
||
'mobile.calendarSubtitle': 'Pagsasanay, torneo, liga at holiday sa buwan.',
|
||
'mobile.calendarToday': 'Ngayon',
|
||
'mobile.calendarAllSourcesFailed': 'Hindi ma-load ang kalendaryo.',
|
||
'mobile.calendarSourceFailed': 'hindi na-load',
|
||
'mobile.calendarLegend': 'Ipakita',
|
||
'mobile.calendarCancellationTitle': 'Kinanselang pagsasanay',
|
||
'mobile.calendarCancellationHint': 'Itinatago ang paulit-ulit na oras sa mga araw na iyon.',
|
||
'mobile.calendarCancellationStart': 'Petsa (YYYY-MM-DD)',
|
||
'mobile.calendarCancellationEnd': 'Hanggang (opsyonal)',
|
||
'mobile.calendarCancellationReason': 'Dahilan',
|
||
'mobile.calendarCancellationSave': 'I-save',
|
||
'mobile.calendarCancellationInMonth': 'Mga kansela ngayong buwan',
|
||
'mobile.calendarGridTitle': 'Buwan',
|
||
'mobile.calendarAgendaTitle': 'Mga appointment sa buwan',
|
||
'mobile.calendarAgendaEmpty': 'Walang appointment sa buwang ito.',
|
||
},
|
||
};
|
||
|
||
function calendarStringsForLocale(code) {
|
||
if (code === 'de' || code === 'de-CH' || code === 'de-extended') return MOBILE_CALENDAR_I18N.de;
|
||
if (code.startsWith('en-')) return MOBILE_CALENDAR_I18N.en;
|
||
if (code === 'es') return MOBILE_CALENDAR_I18N.es;
|
||
if (code === 'fr') return MOBILE_CALENDAR_I18N.fr;
|
||
if (code === 'it') return MOBILE_CALENDAR_I18N.it;
|
||
if (code === 'pl') return MOBILE_CALENDAR_I18N.pl;
|
||
if (code === 'ja') return MOBILE_CALENDAR_I18N.ja;
|
||
if (code === 'zh') return MOBILE_CALENDAR_I18N.zh;
|
||
if (code === 'th') return MOBILE_CALENDAR_I18N.th;
|
||
if (code === 'tl' || code === 'fil') return MOBILE_CALENDAR_I18N[code === 'fil' ? 'fil' : 'tl'];
|
||
return MOBILE_CALENDAR_I18N.en;
|
||
}
|
||
|
||
function flatten(obj, prefix = '', out = {}) {
|
||
for (const [key, value] of Object.entries(obj || {})) {
|
||
const nextKey = prefix ? `${prefix}.${key}` : key;
|
||
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||
flatten(value, nextKey, out);
|
||
} else if (typeof value === 'string') {
|
||
out[nextKey] = value;
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
function escapeKotlinString(value) {
|
||
return value
|
||
.replace(/\\/g, '\\\\')
|
||
.replace(/"/g, '\\"')
|
||
.replace(/\r/g, '\\r')
|
||
.replace(/\n/g, '\\n');
|
||
}
|
||
|
||
function kotlinIdentifier(code) {
|
||
return code.replace(/[^A-Za-z0-9_]/g, '_');
|
||
}
|
||
|
||
function main() {
|
||
const baseJson = JSON.parse(fs.readFileSync(path.join(SOURCE_DIR, 'de.json'), 'utf8'));
|
||
const baseFlat = {
|
||
...flatten(baseJson),
|
||
...MOBILE_STRINGS.de,
|
||
};
|
||
const localeFiles = fs
|
||
.readdirSync(SOURCE_DIR)
|
||
.filter((file) => file.endsWith('.json') && !file.endsWith('.backup'))
|
||
.sort((a, b) => a.localeCompare(b));
|
||
|
||
const lines = [];
|
||
lines.push('package de.tsschulz.tt_tagebuch.shared.i18n');
|
||
lines.push('');
|
||
lines.push('data class SupportedLanguage(val code: String, val label: String)');
|
||
lines.push('');
|
||
lines.push('object MobileStrings {');
|
||
lines.push(' const val DEFAULT_LANGUAGE = "de"');
|
||
lines.push('');
|
||
lines.push(' val supportedLanguages: List<SupportedLanguage> = listOf(');
|
||
for (const file of localeFiles) {
|
||
const code = path.basename(file, '.json');
|
||
lines.push(` SupportedLanguage("${escapeKotlinString(code)}", "${escapeKotlinString(LOCALE_LABELS[code] || code)}"),`);
|
||
}
|
||
lines.push(' )');
|
||
lines.push('');
|
||
const baseEntries = Object.entries(baseFlat).sort(([a], [b]) => a.localeCompare(b));
|
||
for (const file of localeFiles) {
|
||
const code = path.basename(file, '.json');
|
||
const localeJson = JSON.parse(fs.readFileSync(path.join(SOURCE_DIR, file), 'utf8'));
|
||
const specificMobileStrings = MOBILE_STRINGS[code] || (code.startsWith('en-') ? MOBILE_STRINGS.en : {});
|
||
const flat = {
|
||
...baseFlat,
|
||
...flatten(localeJson),
|
||
...specificMobileStrings,
|
||
...calendarStringsForLocale(code),
|
||
};
|
||
lines.push(` private val ${kotlinIdentifier(code)}: Map<String, String> by lazy { mapOf(`);
|
||
for (const [key, value] of Object.entries(flat).sort(([a], [b]) => a.localeCompare(b))) {
|
||
lines.push(` "${escapeKotlinString(key)}" to "${escapeKotlinString(value)}",`);
|
||
}
|
||
lines.push(' ) }');
|
||
lines.push('');
|
||
}
|
||
lines.push(` private val fallback: Map<String, String> get() = ${kotlinIdentifier('de')}`);
|
||
lines.push('');
|
||
lines.push(' fun get(languageCode: String, key: String, fallbackValue: String): String =');
|
||
lines.push(' mapFor(languageCode)[key] ?: fallback[key] ?: fallbackValue');
|
||
lines.push('');
|
||
lines.push(' private fun mapFor(languageCode: String): Map<String, String> = when (languageCode) {');
|
||
for (const file of localeFiles) {
|
||
const code = path.basename(file, '.json');
|
||
lines.push(` "${escapeKotlinString(code)}" -> ${kotlinIdentifier(code)}`);
|
||
}
|
||
lines.push(' else -> fallback');
|
||
lines.push(' }');
|
||
lines.push('}');
|
||
lines.push('');
|
||
|
||
fs.mkdirSync(path.dirname(OUT), { recursive: true });
|
||
fs.writeFileSync(OUT, lines.join('\n'), 'utf8');
|
||
console.log(`Wrote ${OUT} (${localeFiles.length} locales, ${baseEntries.length} keys each)`);
|
||
}
|
||
|
||
main();
|