chore: (#708) Print key validation errors, translate the language dialog

This commit is contained in:
jamesread 2025-11-10 23:26:55 +00:00
parent 67a9d3b1d1
commit 217b17a058
8 changed files with 93 additions and 10 deletions

View File

@ -55,19 +55,19 @@
<dialog ref="languageDialog" class="language-dialog" @click="handleDialogClick"> <dialog ref="languageDialog" class="language-dialog" @click="handleDialogClick">
<div class="dialog-content" @click.stop> <div class="dialog-content" @click.stop>
<h2>Select Language</h2> <h2>{{ t('language-dialog.title') }}</h2>
<select v-model="selectedLanguage" @change="changeLanguage" class="language-select"> <select v-model="selectedLanguage" @change="changeLanguage" class="language-select">
<option v-for="(name, code) in availableLanguages" :key="code" :value="code"> <option v-for="(name, code) in availableLanguages" :key="code" :value="code">
{{ name }} {{ name }}
</option> </option>
</select> </select>
<p class="browser-languages"> <p class="browser-languages">
Browser languages: {{ t('language-dialog.browser-languages') }}:
<span v-if="browserLanguages.length > 0">{{ browserLanguages.join(', ') }}</span> <span v-if="browserLanguages.length > 0">{{ browserLanguages.join(', ') }}</span>
<span v-else>Not available</span> <span v-else>{{ t('language-dialog.not-available') }}</span>
</p> </p>
<div class="dialog-buttons"> <div class="dialog-buttons">
<button @click="closeLanguageDialog">Close</button> <button @click="closeLanguageDialog">{{ t('language-dialog.close') }}</button>
</div> </div>
</div> </div>
</dialog> </dialog>

View File

@ -1 +1 @@
{"messages":{"de-DE":{"connected":"Verbunden","docs":"Dokumentation","login-button":"Login","logs.action":"Aktion","logs.metadata":"Metadaten","logs.no-logs-to-display":"Es gibt keine Protokolle zu anzeigen.","logs.page-description":"Dies ist eine Liste von Protokollen von Aktionen, die ausgeführt wurden. Sie können die Liste nach Aktionstitel filtern.","logs.status":"Status","logs.timestamp":"Zeitstempel","logs.title":"Protokolle","nav.actions":"Aktionen","nav.diagnostics":"Diagnostik","nav.entities":"Entitäten","nav.logs":"Protokolle","raise-issue":"Ein Problem melden auf GitHub","return-to-index":"Zurück zur Startseite","search-filter":"Filter aktuelle Seite","welcome":"Willkommen bei OliveTin"},"en":{"connected":"Connected","docs":"Documentation","login-button":"Login","logs.action":"Action","logs.metadata":"Metadata","logs.no-logs-to-display":"There are no logs to display.","logs.page-description":"This is a list of logs from actions that have been executed. You can filter the list by action title.","logs.status":"Status","logs.timestamp":"Timestamp","logs.title":"Logs","nav.actions":"Actions","nav.diagnostics":"Diagnostics","nav.entities":"Entities","nav.logs":"Logs","raise-issue":"Raise an issue on GitHub","return-to-index":"Return to index","search-filter":"Filter current page","welcome":"Welcome to OliveTin"},"es-ES":{"connected":"Conectado","docs":"Documentación","login-button":"Iniciar sesión","logs.action":"Acción","logs.metadata":"Metadatos","logs.no-logs-to-display":"No hay registros para mostrar.","logs.page-description":"Esta es una lista de registros de acciones que han sido ejecutadas. Puede filtrar la lista por título de acción.","logs.status":"Status","logs.timestamp":"Timestamp","logs.title":"Registros","nav.actions":"Acciones","nav.diagnostics":"Diagnósticos","nav.entities":"Entidades","nav.logs":"Registros","raise-issue":"Reportar un problema en GitHub","return-to-index":"Volver a la página principal","search-filter":"Filtrar página actual","welcome":"Bienvenido a OliveTin"},"it-IT":{"connected":"Connesso","docs":"Documentazione","login-button":"Login","logs.action":"Azione","logs.metadata":"Metadati","logs.no-logs-to-display":"Non ci sono registri da mostrare.","logs.page-description":"Questa è una lista di registri delle azioni che sono state eseguite. Puoi filtrare la lista per titolo dell'azione.","logs.status":"Status","logs.timestamp":"Timestamp","logs.title":"Registri","logs.user":"Utente","nav.actions":"Azioni","nav.diagnostics":"Diagnostica","nav.entities":"Entità","nav.logs":"Registri","raise-issue":"Segnala un problema su GitHub","return-to-index":"Torna alla pagina principale","search-filter":"Filtra la pagina corrente","welcome":"Benvenuto in OliveTin"},"zh-Hans-CN":{"connected":"已连接","docs":"文档","login-button":"登录","logs.action":"动作","logs.metadata":"元数据","logs.no-logs-to-display":"没有日志可显示。","logs.page-description":"这是一个动作执行日志列表。您可以按动作标题过滤列表。","logs.status":"状态","logs.timestamp":"时间戳","logs.title":"日志","nav.actions":"动作","nav.diagnostics":"诊断","nav.entities":"实体","nav.logs":"日志","raise-issue":"报告问题 on GitHub","return-to-index":"返回首页","search-filter":"过滤当前页面","welcome":"欢迎使用 OliveTin"}}} {"messages":{"de-DE":{"connected":"Verbunden","docs":"Dokumentation","language-dialog.browser-languages":"Browser-Sprachen","language-dialog.close":"Schließen","language-dialog.not-available":"Nicht verfügbar","language-dialog.title":"Sprache auswählen","login-button":"Login","logs.action":"Aktion","logs.metadata":"Metadaten","logs.no-logs-to-display":"Es gibt keine Protokolle zu anzeigen.","logs.page-description":"Dies ist eine Liste von Protokollen von Aktionen, die ausgeführt wurden. Sie können die Liste nach Aktionstitel filtern.","logs.status":"Status","logs.timestamp":"Zeitstempel","logs.title":"Protokolle","nav.actions":"Aktionen","nav.diagnostics":"Diagnostik","nav.entities":"Entitäten","nav.logs":"Protokolle","raise-issue":"Ein Problem melden auf GitHub","return-to-index":"Zurück zur Startseite","search-filter":"Filter aktuelle Seite","welcome":"Willkommen bei OliveTin"},"en":{"connected":"Connected","docs":"Documentation","language-dialog.browser-languages":"Browser languages","language-dialog.close":"Close","language-dialog.not-available":"Not available","language-dialog.title":"Select Language","login-button":"Login","logs.action":"Action","logs.metadata":"Metadata","logs.no-logs-to-display":"There are no logs to display.","logs.page-description":"This is a list of logs from actions that have been executed. You can filter the list by action title.","logs.status":"Status","logs.timestamp":"Timestamp","logs.title":"Logs","nav.actions":"Actions","nav.diagnostics":"Diagnostics","nav.entities":"Entities","nav.logs":"Logs","raise-issue":"Raise an issue on GitHub","return-to-index":"Return to index","search-filter":"Filter current page","welcome":"Welcome to OliveTin"},"es-ES":{"connected":"Conectado","docs":"Documentación","language-dialog.browser-languages":"Idiomas del navegador","language-dialog.close":"Cerrar","language-dialog.not-available":"No disponible","language-dialog.title":"Seleccionar idioma","login-button":"Iniciar sesión","logs.action":"Acción","logs.metadata":"Metadatos","logs.no-logs-to-display":"No hay registros para mostrar.","logs.page-description":"Esta es una lista de registros de acciones que han sido ejecutadas. Puede filtrar la lista por título de acción.","logs.status":"Status","logs.timestamp":"Timestamp","logs.title":"Registros","nav.actions":"Acciones","nav.diagnostics":"Diagnósticos","nav.entities":"Entidades","nav.logs":"Registros","raise-issue":"Reportar un problema en GitHub","return-to-index":"Volver a la página principal","search-filter":"Filtrar página actual","welcome":"Bienvenido a OliveTin"},"it-IT":{"connected":"Connesso","docs":"Documentazione","language-dialog.browser-languages":"Lingue del browser","language-dialog.close":"Chiudi","language-dialog.not-available":"Non disponibile","language-dialog.title":"Seleziona lingua","login-button":"Login","logs.action":"Azione","logs.metadata":"Metadati","logs.no-logs-to-display":"Non ci sono registri da mostrare.","logs.page-description":"Questa è una lista di registri delle azioni che sono state eseguite. Puoi filtrare la lista per titolo dell'azione.","logs.status":"Status","logs.timestamp":"Timestamp","logs.title":"Registri","nav.actions":"Azioni","nav.diagnostics":"Diagnostica","nav.entities":"Entità","nav.logs":"Registri","raise-issue":"Segnala un problema su GitHub","return-to-index":"Torna alla pagina principale","search-filter":"Filtra la pagina corrente","welcome":"Benvenuto in OliveTin"},"zh-Hans-CN":{"connected":"已连接","docs":"文档","language-dialog.browser-languages":"浏览器语言","language-dialog.close":"关闭","language-dialog.not-available":"不可用","language-dialog.title":"选择语言","login-button":"登录","logs.action":"动作","logs.metadata":"元数据","logs.no-logs-to-display":"没有日志可显示。","logs.page-description":"这是一个动作执行日志列表。您可以按动作标题过滤列表。","logs.status":"状态","logs.timestamp":"时间戳","logs.title":"日志","nav.actions":"动作","nav.diagnostics":"诊断","nav.entities":"实体","nav.logs":"日志","raise-issue":"在 GitHub 上报告问题","return-to-index":"返回首页","search-filter":"过滤当前页面","welcome":"欢迎使用 OliveTin"}}}

View File

@ -18,3 +18,7 @@ translations:
logs.no-logs-to-display: Es gibt keine Protokolle zu anzeigen. logs.no-logs-to-display: Es gibt keine Protokolle zu anzeigen.
return-to-index: Zurück zur Startseite return-to-index: Zurück zur Startseite
search-filter: Filter aktuelle Seite search-filter: Filter aktuelle Seite
language-dialog.title: Sprache auswählen
language-dialog.browser-languages: Browser-Sprachen
language-dialog.not-available: Nicht verfügbar
language-dialog.close: Schließen

View File

@ -18,3 +18,7 @@ translations:
logs.no-logs-to-display: There are no logs to display. logs.no-logs-to-display: There are no logs to display.
return-to-index: Return to index return-to-index: Return to index
search-filter: Filter current page search-filter: Filter current page
language-dialog.title: Select Language
language-dialog.browser-languages: Browser languages
language-dialog.not-available: Not available
language-dialog.close: Close

View File

@ -18,3 +18,7 @@ translations:
logs.no-logs-to-display: No hay registros para mostrar. logs.no-logs-to-display: No hay registros para mostrar.
return-to-index: Volver a la página principal return-to-index: Volver a la página principal
search-filter: Filtrar página actual search-filter: Filtrar página actual
language-dialog.title: Seleccionar idioma
language-dialog.browser-languages: Idiomas del navegador
language-dialog.not-available: No disponible
language-dialog.close: Cerrar

View File

@ -18,3 +18,7 @@ translations:
logs.no-logs-to-display: Non ci sono registri da mostrare. logs.no-logs-to-display: Non ci sono registri da mostrare.
return-to-index: Torna alla pagina principale return-to-index: Torna alla pagina principale
search-filter: Filtra la pagina corrente search-filter: Filtra la pagina corrente
language-dialog.title: Seleziona lingua
language-dialog.browser-languages: Lingue del browser
language-dialog.not-available: Non disponibile
language-dialog.close: Chiudi

View File

@ -90,9 +90,72 @@ func getCombinedLanguageContent() *CombinedTranslationsOutput {
output.Messages[languageName] = yamlData.Translations output.Messages[languageName] = yamlData.Translations
} }
validateTranslations(output)
return output return output
} }
// getReferenceKeys returns the keys from the "en" translation as the reference set.
func getReferenceKeys(messages map[string]map[string]string) map[string]bool {
enTranslations, exists := messages["en"]
if !exists {
return nil
}
referenceKeys := make(map[string]bool, len(enTranslations))
for key := range enTranslations {
referenceKeys[key] = true
}
return referenceKeys
}
// findMissingKeys returns the keys that are in referenceKeys but not in translations.
func findMissingKeys(referenceKeys map[string]bool, translations map[string]string) []string {
missing := make([]string, 0)
for key := range referenceKeys {
if _, exists := translations[key]; !exists {
missing = append(missing, key)
}
}
return missing
}
// findExtraKeys returns the keys that are in translations but not in referenceKeys.
func findExtraKeys(referenceKeys map[string]bool, translations map[string]string) []string {
extra := make([]string, 0)
for key := range translations {
if !referenceKeys[key] {
extra = append(extra, key)
}
}
return extra
}
// validateTranslations checks all translations against the "en" reference and prints warnings for missing and extra keys.
func validateTranslations(output *CombinedTranslationsOutput) {
referenceKeys := getReferenceKeys(output.Messages)
if referenceKeys == nil {
log.Warnf("No 'en' translation found, skipping validation")
return
}
for langName, translations := range output.Messages {
if langName == "en" {
continue
}
missing := findMissingKeys(referenceKeys, translations)
if len(missing) > 0 {
log.Warnf("Translation '%s' is missing %d key(s): %v", langName, len(missing), missing)
}
extra := findExtraKeys(referenceKeys, translations)
if len(extra) > 0 {
log.Warnf("Translation '%s' has %d extra key(s) not in 'en': %v", langName, len(extra), extra)
}
}
}
func filterLanguageFiles(files []os.DirEntry) []os.DirEntry { func filterLanguageFiles(files []os.DirEntry) []os.DirEntry {
ret := make([]os.DirEntry, 0) ret := make([]os.DirEntry, 0)

View File

@ -18,3 +18,7 @@ translations:
logs.no-logs-to-display: 没有日志可显示。 logs.no-logs-to-display: 没有日志可显示。
return-to-index: 返回首页 return-to-index: 返回首页
search-filter: 过滤当前页面 search-filter: 过滤当前页面
language-dialog.title: 选择语言
language-dialog.browser-languages: 浏览器语言
language-dialog.not-available: 不可用
language-dialog.close: 关闭