From 31f6b9b25b82921d23ddc2a4cb125fc74299b112 Mon Sep 17 00:00:00 2001 From: Antonio Pedro Marques Date: Sat, 27 Apr 2024 02:10:41 +0100 Subject: [PATCH] Translations --- .gitignore | 1 + README.md | 3 + source/app/app.go | 17 ++++- source/app/systray.go | 32 ++++----- source/build.sh | 4 ++ source/config/mac/Settings.taboleta | 3 +- source/libx/translate/translate.go | 107 ++++++++++++++++++++++++++++ source/resources/translations.csv | 21 ++++++ 8 files changed, 169 insertions(+), 19 deletions(-) create mode 100644 source/libx/translate/translate.go create mode 100644 source/resources/translations.csv diff --git a/.gitignore b/.gitignore index 128df84..759886b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ release/ source/config/*/NOTICE source/packaging/*/NOTICE +source/config/*/translations.csv /*.syso /*.sh /*.txt diff --git a/README.md b/README.md index 89406d1..a8debeb 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,9 @@ Radio predefined # delete the sequence "[onAir:]" from song titles Trim \[onAir:.+\] +# language (pt, en) +Language en + # store the clicked titles at $HOME/Musicas.sqlite SQLite $HOME/Musicas.sqlite diff --git a/source/app/app.go b/source/app/app.go index 10fab2c..cdb48d6 100644 --- a/source/app/app.go +++ b/source/app/app.go @@ -13,6 +13,7 @@ import ( "time" "main/libx/taboleta" + "main/libx/translate" "github.com/getlantern/systray" ) @@ -31,6 +32,7 @@ type Settings struct { Playback string Radio string Trim *regexp.Regexp + Language string SQLite string Restart []string Predefined Radio @@ -52,7 +54,9 @@ var SETTINGS Settings var RADIOS []Radio var LARGEST_TITLE_LENGTH = 20 -var musicas = NewMusicas() +var MUSICAS = NewMusicas() + +var TRANSLATOR translate.Translator func Start() { CONFIG_DIR = findConfigDir() @@ -60,6 +64,7 @@ func Start() { log.Printf("SETTINGS: %+v\n", SETTINGS) radios := readRadios(CONFIG_DIR) log.Printf("RADIOS: %+v\n", radios) + TRANSLATOR = translate.NewTranslator(filepath.Join(CONFIG_DIR, "translations.csv"), "pt") checkAddresses := false if checkAddresses { @@ -187,6 +192,8 @@ func readSettings(configDir string) (settings Settings) { settings.Radio = value case "Trim": settings.Trim, _ = regexp.Compile(value) + case "Language": + settings.Language = value case "SQLite": settings.SQLite = value case "Restart": @@ -226,6 +233,14 @@ func readRadios(configDir string) (radios []Radio) { return } +func t(key string) string { + return TRANSLATOR.Translate(SETTINGS.Language, key) +} + +func tf(key string, parameters ...any) string { + return TRANSLATOR.TranslateFormatted(SETTINGS.Language, key, parameters...) +} + func AsInt(s string, onError int) int { i, err := strconv.Atoi(s) if err != nil { diff --git a/source/app/systray.go b/source/app/systray.go index da1a91a..37a2162 100644 --- a/source/app/systray.go +++ b/source/app/systray.go @@ -58,12 +58,12 @@ func buildUI() { //systray.SetTemplateIcon(icon, icon) systray.SetIcon(icon) //systray.SetTooltip("RádioTaboleta") - setMenuTitle("RádioTaboleta", false) + setMenuTitle(t("RádioTaboleta"), false) - mLower = systray.AddMenuItem("Volume -", "Diminuir o volume") + mLower = systray.AddMenuItem(t("Volume -"), t("Diminuir o volume")) mLower.Hide() - mSetVol = systray.AddMenuItem("Volume %", "Definir o volume") + mSetVol = systray.AddMenuItem(t("Volume %"), t("Definir o volume")) mSetVol.Hide() mSetVolClickedCh := make(chan int) mSetVolListen := func(mSetVolAtIndex *systray.MenuItem, vol int) { @@ -83,7 +83,7 @@ func buildUI() { } } - mLouder = systray.AddMenuItem("Volume +", "Aumentar o volume") + mLouder = systray.AddMenuItem(t("Volume +"), t("Aumentar o volume")) mLouder.Hide() systray.AddSeparator() @@ -93,7 +93,7 @@ func buildUI() { mServer.Disable() } - mStatus = systray.AddMenuItem("A iniciar...", "") + mStatus = systray.AddMenuItem(t("A iniciar..."), "") mStatus.Disable() mName = systray.AddMenuItem("", "") @@ -105,22 +105,22 @@ func buildUI() { systray.AddSeparator() - mZapOn = systray.AddMenuItem("Zapping", "Percorrer as estações") + mZapOn = systray.AddMenuItem(t("Zapping"), t("Percorrer as estações")) mZapOn.Hide() - mZapOff = systray.AddMenuItem("Zapping", "Parar de percorrer as estações") + mZapOff = systray.AddMenuItem(t("Zapping"), t("Parar de percorrer as estações")) mZapOff.Check() mZapOff.Hide() - mResume = systray.AddMenuItem("Retomar", "Iniciar o rádio") + mResume = systray.AddMenuItem(t("Retomar"), t("Iniciar o rádio")) mResume.Hide() - mPause = systray.AddMenuItem("Pausa", "Parar o rádio") + mPause = systray.AddMenuItem(t("Pausa"), t("Parar o rádio")) mPause.Hide() systray.AddSeparator() - mClose := systray.AddMenuItem("Sair", "Fechar") + mClose := systray.AddMenuItem(t("Sair"), t("Fechar")) systray.AddSeparator() mRadioClickedCh := make(chan int) @@ -149,7 +149,7 @@ func buildUI() { latest = item } } - mRadioAtIndex := systray.AddMenuItem(radio.Name, "Mudar para "+radio.Name) + mRadioAtIndex := systray.AddMenuItem(radio.Name, tf("Mudar para %s", radio.Name)) latest = item mRadioListen(mRadioAtIndex, i) mRadios = append(mRadios, mRadioAtIndex) @@ -179,7 +179,7 @@ func buildUI() { clipboard.WriteAll(displayedTitle) split := strings.SplitN(displayedTitle, " - ", 2) if len(split) == 2 { - musicas.Add(split[0], split[1]) + MUSICAS.Add(split[0], split[1]) } } @@ -307,17 +307,17 @@ func updateUI() { mZapOn.Hide() mZapOff.Show() } - mStatus.SetTitle(fmt.Sprintf("Vol %d%%, %d kbps", currentVolume, status.BitRate)) + mStatus.SetTitle(tf("Vol %d%%, %d kbps", currentVolume, status.BitRate)) } else { mResume.Show() mPause.Hide() mZapOn.Hide() mZapOff.Hide() - mStatus.SetTitle(fmt.Sprintf("Vol %d%%, em pausa", currentVolume)) + mStatus.SetTitle(tf("Vol %d%%, em pausa", currentVolume)) if len(displayedName) > 0 { setMenuTitle(displayedName, false) } else { - setMenuTitle("Em pausa", false) + setMenuTitle(t("Em pausa"), false) } } mSetVol.Show() @@ -378,7 +378,7 @@ func updateUI() { mVols2[v2].Check() */ } else { - mStatus.SetTitle(fmt.Sprintf("Não contactável")) + mStatus.SetTitle(t("Não contactável")) if len(status.ErrorAddress) > 0 { for i, mRadio := range mRadios { if status.ErrorAddress == RADIOS[i].Address { diff --git a/source/build.sh b/source/build.sh index 05f2b9a..988cdb0 100644 --- a/source/build.sh +++ b/source/build.sh @@ -1,4 +1,5 @@ #!/bin/sh + current=5 cp -va ../NOTICE config/win/ cp -va resources/$current.ico config/win/systray.ico @@ -8,3 +9,6 @@ cp -va ../NOTICE config/mac/ cp -va resources/$current.icns config/mac/menubar.icns cp -va ../NOTICE packaging/mac/Resources/ cp -va resources/$current.icns packaging/mac/Resources/app.icns + +cp -va resources/translations.csv config/win/ +cp -va resources/translations.csv config/mac/ diff --git a/source/config/mac/Settings.taboleta b/source/config/mac/Settings.taboleta index 6b356f8..7e118d2 100644 --- a/source/config/mac/Settings.taboleta +++ b/source/config/mac/Settings.taboleta @@ -3,8 +3,7 @@ Mpd localhost:32123 Volume 9 Zapping 10 -Playback let -Radio let Trim \[onAir:.+\] +Language pt SQLite $HOME/Dropbox/Musicas.sqlite Restart killall -9 mpd diff --git a/source/libx/translate/translate.go b/source/libx/translate/translate.go new file mode 100644 index 0000000..fc62d1b --- /dev/null +++ b/source/libx/translate/translate.go @@ -0,0 +1,107 @@ +package translate + +import ( + "encoding/csv" + "fmt" + "log" + "os" + "strings" +) + +type Translator struct { + Languages []string + keyLanguage string + translations map[string]map[string]string +} + +func NewTranslator(file string, keyLanguage string) Translator { + keyLanguage = languageCode(keyLanguage) + languages, translations, _ := read(file, keyLanguage) + return Translator{Languages: languages, keyLanguage: keyLanguage, translations: translations} +} + +func (self Translator) TranslateFormatted(language string, key string, parameters ...any) string { + return fmt.Sprintf(self.Translate(language, key), parameters...) +} + +func (self Translator) Translate(language string, key string) string { + if len(language) > 0 && language != self.keyLanguage { + if self.translations != nil { + translations := self.translations[language] + if translations != nil { + return translations[key] + } + } + } + return key +} + +func languageCode(s string) string { + return strings.ToLower(strings.TrimSpace(s)) +} + +func read(path string, keyLanguage string) (languages []string, translations map[string]map[string]string, err error) { + file, err := os.Open(path) + if err != nil { + log.Printf("Error reading %s: %s\n", path, err) + return + } + defer file.Close() + + reader := csv.NewReader(file) + reader.Comma = ';' + records, err := reader.ReadAll() + if err != nil { + log.Printf("Error reading %s: %s\n", path, err) + return + } + if len(records) < 2 { + log.Printf("Transalations file %s is empty\n", path) + return + } + if len(records[0]) > 0 { + bom := string([]byte{239, 187, 191}) + records[0][0] = strings.TrimPrefix(records[0][0], bom) + } + + keyIndex := -1 + languages = records[0] + for i, language := range languages { + language = languageCode(language) + fmt.Printf("[%+v][%+v][%+v][%+v][%+v]\n", keyLanguage, language, []byte(language), len(language), + language == keyLanguage) + if language == keyLanguage { + keyIndex = i + } + languages[i] = language + } + + if keyIndex < 0 { + log.Printf("Transalations file %s doesn't contain the key language (%s)\n", path, keyLanguage) + return + } + + translations = make(map[string]map[string]string) + for _, row := range records[1:] { + if len(row) < keyIndex { + continue + } + key := row[keyIndex] + for i, translation := range row { + if i == keyIndex { + continue + } + if i > len(languages) { + continue + } + language := languages[i] + lt := translations[language] + if lt == nil { + lt = make(map[string]string) + } + lt[key] = translation + translations[language] = lt + } + } + return +} diff --git a/source/resources/translations.csv b/source/resources/translations.csv new file mode 100644 index 0000000..e04a57a --- /dev/null +++ b/source/resources/translations.csv @@ -0,0 +1,21 @@ +PT;EN +Volume -;Volume - +Diminuir o volume;Turn volume down +Volume %;Volume % +Definir o volume;Set volume +Volume +;Volume + +Aumentar o volume;Turn volume up +A iniciar...;Starting... +Vol %d%%, %d kbps;Vol %d%%, %d kbps +Vol %d%%, em pausa;Vol %d%%, paused +Iniciar o rádio;Start radio +Pausa;Pause +Em pausa;Paused +Retomar;Resume +Mudar para %s;Switch to %s +Zapping;Zapping +Percorrer as estações;Loop through stations +Parar de percorrer as estações;Stop looping through stations +Parar o rádio;Stop radio +Sair;Exit +Fechar;Close