···7788// Browser is an installed web browser that can open URLs.
99type Browser struct {
1010- ID string // desktop file ID, e.g. "firefox.desktop"
1111- Name string
1212- Icon string // themed icon name; may be empty
1313- Exec string // desktop-entry command line with field codes, e.g. "firefox %u"
1010+ ID string // desktop file ID, e.g. "firefox.desktop"
1111+ Name string
1212+ Icon string
1313+ Exec string
1414+ Actions []Action
1415}
+89
internal/browser/locale.go
···11+// SPDX-License-Identifier: GPL-3.0-or-later
22+33+package browser
44+55+import (
66+ "os"
77+ "strings"
88+99+ "github.com/alyraffauf/goxdgdesktop/desktopfile"
1010+)
1111+1212+// LocalizedString resolves key for the current Desktop Entry locale.
1313+func LocalizedString(file *desktopfile.File, section, key string) string {
1414+ for _, candidate := range localeKeyCandidates(key) {
1515+ if value, ok := file.Get(section, candidate); ok && value != "" {
1616+ return value
1717+ }
1818+ }
1919+ return ""
2020+}
2121+2222+// LocalizedActions resolves each action name for the current locale.
2323+func LocalizedActions(file *desktopfile.File) []Action {
2424+ actions := file.Actions()
2525+ for i := range actions {
2626+ section := desktopfile.ActionSectionStart + actions[i].ID
2727+ if name := LocalizedString(file, section, "Name"); name != "" {
2828+ actions[i].Name = name
2929+ }
3030+ }
3131+ return actions
3232+}
3333+3434+// For lang_COUNTRY@MODIFIER, the spec order is Key[lang_COUNTRY@MODIFIER],
3535+// Key[lang_COUNTRY], Key[lang@MODIFIER], Key[lang], then Key.
3636+func localeKeyCandidates(key string) []string {
3737+ var candidates []string
3838+ for _, loc := range preferredLocales() {
3939+ lang, country, modifier := splitLocale(loc)
4040+ if lang == "" || lang == "C" || lang == "POSIX" {
4141+ continue
4242+ }
4343+ if country != "" && modifier != "" {
4444+ candidates = append(candidates, key+"["+lang+"_"+country+"@"+modifier+"]")
4545+ }
4646+ if country != "" {
4747+ candidates = append(candidates, key+"["+lang+"_"+country+"]")
4848+ }
4949+ if modifier != "" {
5050+ candidates = append(candidates, key+"["+lang+"@"+modifier+"]")
5151+ }
5252+ candidates = append(candidates, key+"["+lang+"]")
5353+ }
5454+ return append(candidates, key)
5555+}
5656+5757+func preferredLocales() []string {
5858+ if language := os.Getenv("LANGUAGE"); language != "" {
5959+ var locales []string
6060+ for _, loc := range strings.Split(language, ":") {
6161+ if loc = strings.TrimSpace(loc); loc != "" {
6262+ locales = append(locales, loc)
6363+ }
6464+ }
6565+ if len(locales) > 0 {
6666+ return locales
6767+ }
6868+ }
6969+ for _, env := range []string{"LC_ALL", "LC_MESSAGES", "LANG"} {
7070+ if value := os.Getenv(env); value != "" {
7171+ return []string{value}
7272+ }
7373+ }
7474+ return nil
7575+}
7676+7777+func splitLocale(loc string) (lang, country, modifier string) {
7878+ if i := strings.IndexByte(loc, '@'); i >= 0 {
7979+ modifier = loc[i+1:]
8080+ loc = loc[:i]
8181+ }
8282+ if i := strings.IndexByte(loc, '.'); i >= 0 {
8383+ loc = loc[:i]
8484+ }
8585+ if i := strings.IndexByte(loc, '_'); i >= 0 {
8686+ return loc[:i], loc[i+1:], modifier
8787+ }
8888+ return loc, "", modifier
8989+}