mirror of
https://github.com/OliveTin/OliveTin
synced 2025-12-12 00:55:34 +00:00
chore: Better fixups on lang tool and support
This commit is contained in:
@@ -84,6 +84,7 @@ import { UserCircle02Icon } from '@hugeicons/core-free-icons'
|
||||
import { DashboardSquare01Icon } from '@hugeicons/core-free-icons'
|
||||
import logoUrl from '../../OliveTinLogo.png';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import combinedTranslations from '../../../lang/combined_output.json';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
@@ -129,13 +130,25 @@ const currentLanguageName = computed(() => {
|
||||
return availableLanguages[languagePreference.value] || languagePreference.value
|
||||
})
|
||||
|
||||
function getBrowserLanguage() {
|
||||
if (navigator.languages && navigator.languages.length > 0) {
|
||||
return navigator.languages[0]
|
||||
}
|
||||
function normalizeBrowserLanguage() {
|
||||
const available = Object.keys(combinedTranslations.messages || {})
|
||||
|
||||
if (navigator.language) {
|
||||
return navigator.language
|
||||
if (navigator.languages && navigator.languages.length > 0) {
|
||||
for (const candidate of navigator.languages) {
|
||||
const lowerCandidate = candidate.toLowerCase()
|
||||
|
||||
// Try exact match (case-insensitive)
|
||||
const exact = available.find(locale => locale.toLowerCase() === lowerCandidate)
|
||||
if (exact) {
|
||||
return exact
|
||||
}
|
||||
|
||||
// Try prefix match (e.g., "zh-CN" -> "zh-Hans-CN")
|
||||
const prefix = available.find(locale => locale.toLowerCase().startsWith(lowerCandidate.split('-')[0] + '-'))
|
||||
if (prefix) {
|
||||
return prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 'en'
|
||||
@@ -235,7 +248,7 @@ function changeLanguage() {
|
||||
if (selectedLanguage.value === 'auto') {
|
||||
localStorage.removeItem('olivetin-language')
|
||||
languagePreference.value = 'auto'
|
||||
window.i18n.locale.value = getBrowserLanguage()
|
||||
window.i18n.locale.value = normalizeBrowserLanguage()
|
||||
} else {
|
||||
window.i18n.locale.value = selectedLanguage.value
|
||||
localStorage.setItem('olivetin-language', selectedLanguage.value)
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
d="m19.6 21l-6.3-6.3q-.75.6-1.725.95T9.5 16q-2.725 0-4.612-1.888T3 9.5t1.888-4.612T9.5 3t4.613 1.888T16 9.5q0 1.1-.35 2.075T14.7 13.3l6.3 6.3zM9.5 14q1.875 0 3.188-1.312T14 9.5t-1.312-3.187T9.5 5T6.313 6.313T5 9.5t1.313 3.188T9.5 14" />
|
||||
</svg>
|
||||
<input :placeholder="t('search-filter')" v-model="searchText" />
|
||||
<button title="Clear search filter" :disabled="!searchText" @click="clearSearch">
|
||||
<button :title="t('logs.clear-filter')" :disabled="!searchText" @click="clearSearch">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24">
|
||||
<path fill="currentColor"
|
||||
d="M19 6.41L17.59 5L12 10.59L6.41 5L5 6.41L10.59 12L5 17.59L6.41 19L12 13.41L17.59 19L19 17.59L13.41 12z" />
|
||||
|
||||
@@ -16,7 +16,7 @@ your browser reports by opening the "Select Language" dialog from the footer.
|
||||
Internally, OliveTin uses the vue-i18n library for translations. This does support
|
||||
language pluralization and other advanced features. For docs, check the following;
|
||||
|
||||
https://vue-i18n.intlify.dev/guide/essentials/pluralization.html
|
||||
[Vue i18n Pluralization Guide](https://vue-i18n.intlify.dev/guide/essentials/pluralization.html)
|
||||
|
||||
The translation files are in YAML format. Each file contains key-value pairs.
|
||||
|
||||
@@ -24,7 +24,7 @@ OliveTin developers then "process" these files into JSON format used for the app
|
||||
|
||||
If you are able, it would be appreciated if you run `make` in the language directory
|
||||
to process your language file before submitting a PR. This will ensure that the JSON
|
||||
file is up to date. If you don't understand how to do this, don't worry; just submit
|
||||
file is up-to-date. If you don't understand how to do this, don't worry; just submit
|
||||
the YAML file and the developers will take care of it.
|
||||
|
||||
## Contributing improvements
|
||||
|
||||
@@ -1 +1,150 @@
|
||||
{"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.blocked":"Blockiert","logs.completed":"Abgeschlossen","logs.exit-code":"Ausführungscode","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.timed-out":"Zeitüberschreitung","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.blocked":"Blocked","logs.completed":"Completed","logs.exit-code":"Exit code","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.timed-out":"Timed out","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.blocked":"Bloqueado","logs.completed":"Completado","logs.exit-code":"Código de salida","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.timed-out":"Tiempo agotado","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.blocked":"Bloccato","logs.completed":"Completato","logs.exit-code":"Codice di uscita","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.timed-out":"Tempo scaduto","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.blocked":"阻塞","logs.completed":"完成","logs.exit-code":"退出代码","logs.metadata":"元数据","logs.no-logs-to-display":"没有日志可显示。","logs.page-description":"这是一个动作执行日志列表。您可以按动作标题过滤列表。","logs.status":"状态","logs.timed-out":"超时","logs.timestamp":"时间戳","logs.title":"日志","nav.actions":"动作","nav.diagnostics":"诊断","nav.entities":"实体","nav.logs":"日志","raise-issue":"在 GitHub 上报告问题","return-to-index":"返回首页","search-filter":"过滤当前页面","welcome":"欢迎使用 OliveTin"}}}
|
||||
{
|
||||
"_comment": "This file is generated. Please re-generate this file using 'make' when you update a translation.",
|
||||
"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.blocked": "Blockiert",
|
||||
"logs.clear-filter": "Suchfilter löschen",
|
||||
"logs.completed": "Abgeschlossen",
|
||||
"logs.exit-code": "Ausführungscode",
|
||||
"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.timed-out": "Zeitüberschreitung",
|
||||
"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.blocked": "Blocked",
|
||||
"logs.clear-filter": "Clear search filter",
|
||||
"logs.completed": "Completed",
|
||||
"logs.exit-code": "Exit code",
|
||||
"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.timed-out": "Timed out",
|
||||
"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.blocked": "Bloqueado",
|
||||
"logs.clear-filter": "Limpiar filtro de búsqueda",
|
||||
"logs.completed": "Completado",
|
||||
"logs.exit-code": "Código de salida",
|
||||
"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": "Estado",
|
||||
"logs.timed-out": "Tiempo agotado",
|
||||
"logs.timestamp": "Marca de tiempo",
|
||||
"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.blocked": "Bloccato",
|
||||
"logs.clear-filter": "Cancella filtro di ricerca",
|
||||
"logs.completed": "Completato",
|
||||
"logs.exit-code": "Codice di uscita",
|
||||
"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": "Stato",
|
||||
"logs.timed-out": "Tempo scaduto",
|
||||
"logs.timestamp": "Date e ora",
|
||||
"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.blocked": "阻塞",
|
||||
"logs.clear-filter": "清除搜索筛选器",
|
||||
"logs.completed": "完成",
|
||||
"logs.exit-code": "退出代码",
|
||||
"logs.metadata": "元数据",
|
||||
"logs.no-logs-to-display": "没有日志可显示。",
|
||||
"logs.page-description": "这是一个动作执行日志列表。您可以按动作标题过滤列表。",
|
||||
"logs.status": "状态",
|
||||
"logs.timed-out": "超时",
|
||||
"logs.timestamp": "时间戳",
|
||||
"logs.title": "日志",
|
||||
"nav.actions": "动作",
|
||||
"nav.diagnostics": "诊断",
|
||||
"nav.entities": "实体",
|
||||
"nav.logs": "日志",
|
||||
"raise-issue": "在 GitHub 上报告问题",
|
||||
"return-to-index": "返回首页",
|
||||
"search-filter": "过滤当前页面",
|
||||
"welcome": "欢迎使用 OliveTin"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ translations:
|
||||
logs.blocked: Blockiert
|
||||
logs.exit-code: Ausführungscode
|
||||
logs.completed: Abgeschlossen
|
||||
logs.clear-filter: Suchfilter löschen
|
||||
return-to-index: Zurück zur Startseite
|
||||
search-filter: Filter aktuelle Seite
|
||||
language-dialog.title: Sprache auswählen
|
||||
|
||||
@@ -20,6 +20,7 @@ translations:
|
||||
logs.blocked: Blocked
|
||||
logs.exit-code: Exit code
|
||||
logs.completed: Completed
|
||||
logs.clear-filter: Clear search filter
|
||||
return-to-index: Return to index
|
||||
search-filter: Filter current page
|
||||
language-dialog.title: Select Language
|
||||
|
||||
@@ -11,15 +11,16 @@ translations:
|
||||
docs: Documentación
|
||||
logs.title: Registros
|
||||
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.timestamp: Timestamp
|
||||
logs.timestamp: Marca de tiempo
|
||||
logs.action: Acción
|
||||
logs.metadata: Metadatos
|
||||
logs.status: Status
|
||||
logs.status: Estado
|
||||
logs.no-logs-to-display: No hay registros para mostrar.
|
||||
logs.timed-out: Tiempo agotado
|
||||
logs.blocked: Bloqueado
|
||||
logs.exit-code: Código de salida
|
||||
logs.completed: Completado
|
||||
logs.clear-filter: Limpiar filtro de búsqueda
|
||||
return-to-index: Volver a la página principal
|
||||
search-filter: Filtrar página actual
|
||||
language-dialog.title: Seleccionar idioma
|
||||
|
||||
@@ -11,15 +11,16 @@ translations:
|
||||
raise-issue: Segnala un problema su GitHub
|
||||
logs.title: Registri
|
||||
logs.page-description: Questa è una lista di registri delle azioni che sono state eseguite. Puoi filtrare la lista per titolo dell'azione.
|
||||
logs.timestamp: Timestamp
|
||||
logs.timestamp: Date e ora
|
||||
logs.action: Azione
|
||||
logs.metadata: Metadati
|
||||
logs.status: Status
|
||||
logs.status: Stato
|
||||
logs.no-logs-to-display: Non ci sono registri da mostrare.
|
||||
logs.timed-out: Tempo scaduto
|
||||
logs.blocked: Bloccato
|
||||
logs.exit-code: Codice di uscita
|
||||
logs.completed: Completato
|
||||
logs.clear-filter: Cancella filtro di ricerca
|
||||
return-to-index: Torna alla pagina principale
|
||||
search-filter: Filtra la pagina corrente
|
||||
language-dialog.title: Seleziona lingua
|
||||
|
||||
42
lang/main.go
42
lang/main.go
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -18,13 +19,16 @@ type LanguageFilev1 struct {
|
||||
}
|
||||
|
||||
type CombinedTranslationsOutput struct {
|
||||
Comment string `json:"_comment"`
|
||||
Messages map[string]map[string]string `json:"messages"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
combinedContent := getCombinedLanguageContent()
|
||||
|
||||
jsonData, err := json.Marshal(combinedContent)
|
||||
sortedContent := sortTranslations(combinedContent)
|
||||
|
||||
jsonData, err := json.MarshalIndent(sortedContent, "", " ")
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Error marshalling combined language content: %v", err)
|
||||
@@ -39,6 +43,41 @@ func main() {
|
||||
log.Infof("Combined language content saved to combined_output.json")
|
||||
}
|
||||
|
||||
// sortTranslations creates a new structure with sorted keys for deterministic output.
|
||||
func sortTranslations(input *CombinedTranslationsOutput) *CombinedTranslationsOutput {
|
||||
sorted := &CombinedTranslationsOutput{
|
||||
Comment: input.Comment,
|
||||
Messages: make(map[string]map[string]string),
|
||||
}
|
||||
|
||||
// Sort language names
|
||||
langNames := make([]string, 0, len(input.Messages))
|
||||
for langName := range input.Messages {
|
||||
langNames = append(langNames, langName)
|
||||
}
|
||||
sort.Strings(langNames)
|
||||
|
||||
// For each language, sort the translation keys
|
||||
for _, langName := range langNames {
|
||||
translations := input.Messages[langName]
|
||||
sortedTranslations := make(map[string]string)
|
||||
|
||||
keys := make([]string, 0, len(translations))
|
||||
for key := range translations {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
sortedTranslations[key] = translations[key]
|
||||
}
|
||||
|
||||
sorted.Messages[langName] = sortedTranslations
|
||||
}
|
||||
|
||||
return sorted
|
||||
}
|
||||
|
||||
func getLanguageDir() string {
|
||||
dirsToSearch := []string{
|
||||
"../lang",
|
||||
@@ -53,6 +92,7 @@ func getLanguageDir() string {
|
||||
|
||||
func getCombinedLanguageContent() *CombinedTranslationsOutput {
|
||||
output := &CombinedTranslationsOutput{
|
||||
Comment: "This file is generated. Please re-generate this file using 'make' when you update a translation.",
|
||||
Messages: make(map[string]map[string]string),
|
||||
}
|
||||
|
||||
|
||||
@@ -25,4 +25,5 @@ translations:
|
||||
logs.timed-out: 超时
|
||||
logs.blocked: 阻塞
|
||||
logs.exit-code: 退出代码
|
||||
logs.completed: 完成
|
||||
logs.completed: 完成
|
||||
logs.clear-filter: 清除搜索筛选器
|
||||
Reference in New Issue
Block a user