120 lines
3.7 KiB
Go
120 lines
3.7 KiB
Go
// Fenrir console screen reader.
|
|
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify it
|
|
// under the terms of the GNU General Public License as published by the Free
|
|
// Software Foundation, either version 3 of the License, or (at your option)
|
|
// any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
// more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along with
|
|
// this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
|
|
|
// Package i18n combines installed and bundled Fenrir gettext catalogs. Screen
|
|
// contents and internal command/cue IDs must never pass through this package.
|
|
package i18n
|
|
|
|
import (
|
|
"embed"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync/atomic"
|
|
|
|
"github.com/leonelquinteros/gotext"
|
|
)
|
|
|
|
const Domain = "fenrir"
|
|
const LocaleDir = "/usr/share/locale"
|
|
|
|
//go:embed locale/*/LC_MESSAGES/fenrir.po
|
|
var bundled embed.FS
|
|
|
|
type Catalog struct{ locales []*gotext.Locale }
|
|
|
|
var current atomic.Pointer[Catalog]
|
|
|
|
// Language follows Python gettext's environment precedence. LANGUAGE may list
|
|
// several languages separated by colons; C/POSIX stop catalog fallback.
|
|
func Language() string {
|
|
for _, name := range []string{"LANGUAGE", "LC_ALL", "LC_MESSAGES", "LANG"} {
|
|
if value := os.Getenv(name); value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return "C"
|
|
}
|
|
|
|
func New(root, languages string) *Catalog {
|
|
catalog := &Catalog{}
|
|
for _, language := range strings.Split(languages, ":") {
|
|
language = strings.TrimSpace(language)
|
|
if index := strings.IndexByte(language, '.'); index >= 0 {
|
|
modifier := ""
|
|
if at := strings.IndexByte(language[index:], '@'); at >= 0 {
|
|
modifier = language[index+at:]
|
|
}
|
|
language = language[:index] + modifier
|
|
}
|
|
if language == "C" || language == "POSIX" {
|
|
break
|
|
}
|
|
if language == "" || strings.ContainsAny(language, "/\\") {
|
|
continue
|
|
}
|
|
locale := gotext.NewLocale(root, language)
|
|
locale.AddDomain(Domain)
|
|
catalog.locales = append(catalog.locales, locale)
|
|
// Keep each language's additions ahead of the next LANGUAGE fallback.
|
|
// Installed contributor translations retain priority within a language.
|
|
local := gotext.NewLocaleFSWithPath(language, bundled, "locale")
|
|
local.AddDomain(Domain)
|
|
catalog.locales = append(catalog.locales, local)
|
|
}
|
|
return catalog
|
|
}
|
|
|
|
// Use installs an immutable catalog and returns the previous one. Configure
|
|
// once at startup; no catalog loading belongs on the input/event path.
|
|
func Use(catalog *Catalog) *Catalog { return current.Swap(catalog) }
|
|
|
|
func T(message string) string {
|
|
if catalog := current.Load(); catalog != nil {
|
|
for _, locale := range catalog.locales {
|
|
if locale.IsTranslated(message) {
|
|
return locale.Get(message)
|
|
}
|
|
}
|
|
}
|
|
return message
|
|
}
|
|
|
|
func N(singular, plural string, count int) string {
|
|
if catalog := current.Load(); catalog != nil {
|
|
for _, locale := range catalog.locales {
|
|
if locale.IsTranslatedN(singular, count) {
|
|
return locale.GetN(singular, plural, count)
|
|
}
|
|
}
|
|
}
|
|
if count == 1 {
|
|
return singular
|
|
}
|
|
return plural
|
|
}
|
|
|
|
// Format substitutes Python-style positional fields after lookup. Keeping
|
|
// {0}/{1} intact lets the Go reader reuse existing catalog message IDs.
|
|
func Format(message string, fields ...string) string {
|
|
replacements := []string{"{{", "{", "}}", "}"}
|
|
for index, value := range fields {
|
|
replacements = append(replacements, "{"+strconv.Itoa(index)+"}", value)
|
|
}
|
|
return strings.NewReplacer(replacements...).Replace(T(message))
|
|
}
|