build(deps): bump github.com/leonelquinteros/gotext

Bumps [github.com/leonelquinteros/gotext](https://github.com/leonelquinteros/gotext) from 1.5.3-0.20230317130943-71a59c05b2c1 to 1.6.0.
- [Release notes](https://github.com/leonelquinteros/gotext/releases)
- [Commits](https://github.com/leonelquinteros/gotext/commits/v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/leonelquinteros/gotext
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2024-04-11 06:15:34 +00:00
committed by Ralf Haferkamp
parent ed79f3e012
commit 2de9f15828
11 changed files with 521 additions and 111 deletions

2
go.mod
View File

@@ -55,7 +55,7 @@ require (
github.com/jellydator/ttlcache/v3 v3.2.0
github.com/jinzhu/now v1.1.5
github.com/justinas/alice v1.2.0
github.com/leonelquinteros/gotext v1.5.3-0.20230317130943-71a59c05b2c1
github.com/leonelquinteros/gotext v1.6.0
github.com/libregraph/idm v0.4.1-0.20240410123343-a51b459380d0
github.com/libregraph/lico v0.61.3-0.20240322112242-72cf9221d3a7
github.com/mitchellh/mapstructure v1.5.0

4
go.sum
View File

@@ -1632,8 +1632,8 @@ github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvf
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/leonelquinteros/gotext v1.5.3-0.20230317130943-71a59c05b2c1 h1:k56sFOOJ0CYuQtGoRSeAMhP1R692+iNH+S1dC/CEz0w=
github.com/leonelquinteros/gotext v1.5.3-0.20230317130943-71a59c05b2c1/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M=
github.com/leonelquinteros/gotext v1.6.0 h1:IYL2+dKsaYYvqGAOafaC7mpAGBhMrD/vKjHUGyp8V64=
github.com/leonelquinteros/gotext v1.6.0/go.mod h1:qQRISjoonXYFdRGrTG1LARQ38Gpibad0IPeB4hpvyyM=
github.com/libregraph/idm v0.4.1-0.20240410123343-a51b459380d0 h1:NC6JoX08mr2WOVyplqbLUFFZuGJQHc/Xzsbtcpz0aqA=
github.com/libregraph/idm v0.4.1-0.20240410123343-a51b459380d0/go.mod h1:taEqhdiG7u1aXxPNAi1+IQUpxDP0JpoDGrwhfpmHlhs=
github.com/libregraph/lico v0.61.3-0.20240322112242-72cf9221d3a7 h1:fcPgiBu7DGyGeokE0Qk+S+GW/3n+QWu1dIjw0TqadhI=

View File

@@ -3,14 +3,13 @@ package gotext
import (
"bytes"
"encoding/gob"
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"golang.org/x/text/language"
"github.com/leonelquinteros/gotext/plurals"
)
@@ -21,7 +20,6 @@ type Domain struct {
// Language header
Language string
tag language.Tag
// Plural-Forms header
PluralForms string
@@ -35,9 +33,9 @@ type Domain struct {
pluralforms plurals.Expression
// Storage
translations map[string]*Translation
contexts map[string]map[string]*Translation
pluralTranslations map[string]*Translation
translations map[string]*Translation
contextTranslations map[string]map[string]*Translation
pluralTranslations map[string]*Translation
// Sync Mutex
trMutex sync.RWMutex
@@ -84,7 +82,7 @@ func NewDomain() *Domain {
domain.Headers = make(HeaderMap)
domain.headerComments = make([]string, 0)
domain.translations = make(map[string]*Translation)
domain.contexts = make(map[string]map[string]*Translation)
domain.contextTranslations = make(map[string]map[string]*Translation)
domain.pluralTranslations = make(map[string]*Translation)
return domain
@@ -142,7 +140,6 @@ func (do *Domain) parseHeaders() {
// Get/save needed headers
do.Language = do.Headers.Get(languageKey)
do.tag = language.Make(do.Language)
do.PluralForms = do.Headers.Get(pluralFormsKey)
// Parse Plural-Forms formula
@@ -183,14 +180,14 @@ func (do *Domain) DropStaleTranslations() {
defer do.trMutex.Unlock()
defer do.pluralMutex.Unlock()
for name, ctx := range do.contexts {
for name, ctx := range do.contextTranslations {
for id, trans := range ctx {
if trans.IsStale() {
delete(ctx, id)
}
}
if len(ctx) == 0 {
delete(do.contexts, name)
delete(do.contextTranslations, name)
}
}
@@ -312,7 +309,7 @@ func (do *Domain) SetC(id, ctx, str string) {
defer do.trMutex.Unlock()
defer do.pluralMutex.Unlock()
if context, ok := do.contexts[ctx]; ok {
if context, ok := do.contextTranslations[ctx]; ok {
if trans, hasTrans := context[id]; hasTrans {
trans.Set(str)
} else {
@@ -325,7 +322,7 @@ func (do *Domain) SetC(id, ctx, str string) {
trans := NewTranslation()
trans.ID = id
trans.Set(str)
do.contexts[ctx] = map[string]*Translation{
do.contextTranslations[ctx] = map[string]*Translation{
id: trans,
}
}
@@ -337,11 +334,11 @@ func (do *Domain) GetC(str, ctx string, vars ...interface{}) string {
do.trMutex.RLock()
defer do.trMutex.RUnlock()
if do.contexts != nil {
if _, ok := do.contexts[ctx]; ok {
if do.contexts[ctx] != nil {
if _, ok := do.contexts[ctx][str]; ok {
return Printf(do.contexts[ctx][str].Get(), vars...)
if do.contextTranslations != nil {
if _, ok := do.contextTranslations[ctx]; ok {
if do.contextTranslations[ctx] != nil {
if _, ok := do.contextTranslations[ctx][str]; ok {
return Printf(do.contextTranslations[ctx][str].Get(), vars...)
}
}
}
@@ -361,7 +358,7 @@ func (do *Domain) SetNC(id, plural, ctx string, n int, str string) {
defer do.trMutex.Unlock()
defer do.pluralMutex.Unlock()
if context, ok := do.contexts[ctx]; ok {
if context, ok := do.contextTranslations[ctx]; ok {
if trans, hasTrans := context[id]; hasTrans {
trans.SetN(pluralForm, str)
} else {
@@ -374,7 +371,7 @@ func (do *Domain) SetNC(id, plural, ctx string, n int, str string) {
trans := NewTranslation()
trans.ID = id
trans.SetN(pluralForm, str)
do.contexts[ctx] = map[string]*Translation{
do.contextTranslations[ctx] = map[string]*Translation{
id: trans,
}
}
@@ -386,11 +383,11 @@ func (do *Domain) GetNC(str, plural string, n int, ctx string, vars ...interface
do.trMutex.RLock()
defer do.trMutex.RUnlock()
if do.contexts != nil {
if _, ok := do.contexts[ctx]; ok {
if do.contexts[ctx] != nil {
if _, ok := do.contexts[ctx][str]; ok {
return Printf(do.contexts[ctx][str].GetN(do.pluralForm(n)), vars...)
if do.contextTranslations != nil {
if _, ok := do.contextTranslations[ctx]; ok {
if do.contextTranslations[ctx] != nil {
if _, ok := do.contextTranslations[ctx][str]; ok {
return Printf(do.contextTranslations[ctx][str].GetN(do.pluralForm(n)), vars...)
}
}
}
@@ -402,7 +399,51 @@ func (do *Domain) GetNC(str, plural string, n int, ctx string, vars ...interface
return Printf(plural, vars...)
}
//GetTranslations returns a copy of every translation in the domain. It does not support contexts.
// IsTranslated reports whether a string is translated
func (do *Domain) IsTranslated(str string) bool {
return do.IsTranslatedN(str, 0)
}
// IsTranslatedN reports whether a plural string is translated
func (do *Domain) IsTranslatedN(str string, n int) bool {
do.trMutex.RLock()
defer do.trMutex.RUnlock()
if do.translations == nil {
return false
}
tr, ok := do.translations[str]
if !ok {
return false
}
return tr.IsTranslatedN(n)
}
// IsTranslatedC reports whether a context string is translated
func (do *Domain) IsTranslatedC(str, ctx string) bool {
return do.IsTranslatedNC(str, 0, ctx)
}
// IsTranslatedNC reports whether a plural context string is translated
func (do *Domain) IsTranslatedNC(str string, n int, ctx string) bool {
do.trMutex.RLock()
defer do.trMutex.RUnlock()
if do.contextTranslations == nil {
return false
}
translations, ok := do.contextTranslations[ctx]
if !ok {
return false
}
tr, ok := translations[str]
if !ok {
return false
}
return tr.IsTranslatedN(n)
}
// GetTranslations returns a copy of every translation in the domain. It does not support contexts.
func (do *Domain) GetTranslations() map[string]*Translation {
all := make(map[string]*Translation, len(do.translations))
@@ -511,7 +552,7 @@ func (do *Domain) MarshalText() ([]byte, error) {
// Just as with headers, output translations in consistent order (to minimise diffs between round-trips), with (first) source reference taking priority, followed by context and finally ID
references := make([]SourceReference, 0)
for name, ctx := range do.contexts {
for name, ctx := range do.contextTranslations {
for id, trans := range ctx {
if id == "" {
continue
@@ -609,9 +650,39 @@ func (do *Domain) MarshalText() ([]byte, error) {
}
func EscapeSpecialCharacters(s string) string {
s = regexp.MustCompile(`([^\\])(")`).ReplaceAllString(s, "$1\\\"") // Escape non-escaped double quotation marks
s = strings.ReplaceAll(s, "\n", "\"\n\"") // Convert newlines into multi-line strings
return s
s = regexp.MustCompile(`([^\\])(")`).ReplaceAllString(s, "$1\\\"") // Escape non-escaped double quotation marks
if strings.Count(s, "\n") == 0 {
return s
}
// Handle EOL and multi-lines
// Only one line, but finishing with \n
if strings.Count(s, "\n") == 1 && strings.HasSuffix(s, "\n") {
return strings.ReplaceAll(s, "\n", "\\n")
}
elems := strings.Split(s, "\n")
// Skip last element for multiline which is an empty
var shouldEndWithEOL bool
if elems[len(elems)-1] == "" {
elems = elems[:len(elems)-1]
shouldEndWithEOL = true
}
data := []string{(`"`)}
for i, v := range elems {
l := fmt.Sprintf(`"%s\n"`, v)
// Last element without EOL
if i == len(elems)-1 && !shouldEndWithEOL {
l = fmt.Sprintf(`"%s"`, v)
}
// Remove finale " to last element as the whole string will be quoted
if i == len(elems)-1 {
l = strings.TrimSuffix(l, `"`)
}
data = append(data, l)
}
return strings.Join(data, "\n")
}
// MarshalBinary implements encoding.BinaryMarshaler interface
@@ -623,7 +694,7 @@ func (do *Domain) MarshalBinary() ([]byte, error) {
obj.Nplurals = do.nplurals
obj.Plural = do.plural
obj.Translations = do.translations
obj.Contexts = do.contexts
obj.Contexts = do.contextTranslations
var buff bytes.Buffer
encoder := gob.NewEncoder(&buff)
@@ -649,7 +720,7 @@ func (do *Domain) UnmarshalBinary(data []byte) error {
do.nplurals = obj.Nplurals
do.plural = obj.Plural
do.translations = obj.Translations
do.contexts = obj.Contexts
do.contextTranslations = obj.Contexts
if expr, err := plurals.Compile(do.plural); err == nil {
do.pluralforms = expr

View File

@@ -24,6 +24,7 @@ package gotext
import (
"encoding/gob"
"strings"
"sync"
)
@@ -31,47 +32,56 @@ import (
type config struct {
sync.RWMutex
// Path to library directory where all locale directories and Translation files are.
library string
// Default domain to look at when no domain is specified. Used by package level functions.
domain string
// Language set.
language string
// Path to library directory where all locale directories and Translation files are.
library string
languages []string
// Storage for package level methods
storage *Locale
locales []*Locale
}
var globalConfig *config
var FallbackLocale = "en_US"
func init() {
// Init default configuration
globalConfig = &config{
domain: "default",
language: "en_US",
library: "/usr/local/share/locale",
storage: nil,
domain: "default",
languages: []string{FallbackLocale},
library: "/usr/local/share/locale",
locales: nil,
}
// Register Translator types for gob encoding
gob.Register(TranslatorEncoding{})
}
// loadStorage creates a new Locale object at package level based on the Global variables settings.
// It's called automatically when trying to use Get or GetD methods.
func loadStorage(force bool) {
// loadLocales creates a new Locale object for every language (specified using Configure)
// at package level based on the configuration of global configuration .
// It is called when trying to use Get or GetD methods.
func loadLocales(rebuildCache bool) {
globalConfig.Lock()
if globalConfig.storage == nil || force {
globalConfig.storage = NewLocale(globalConfig.library, globalConfig.language)
if globalConfig.locales == nil || rebuildCache {
var locales []*Locale
for _, language := range globalConfig.languages {
locales = append(locales, NewLocale(globalConfig.library, language))
}
globalConfig.locales = locales
}
if _, ok := globalConfig.storage.Domains[globalConfig.domain]; !ok || force {
globalConfig.storage.AddDomain(globalConfig.domain)
for _, locale := range globalConfig.locales {
if _, ok := locale.Domains[globalConfig.domain]; !ok || rebuildCache {
locale.AddDomain(globalConfig.domain)
}
locale.SetDomain(globalConfig.domain)
}
globalConfig.storage.SetDomain(globalConfig.domain)
globalConfig.Unlock()
}
@@ -80,8 +90,9 @@ func loadStorage(force bool) {
func GetDomain() string {
var dom string
globalConfig.RLock()
if globalConfig.storage != nil {
dom = globalConfig.storage.GetDomain()
if globalConfig.locales != nil {
// All locales have the same domain
dom = globalConfig.locales[0].GetDomain()
}
if dom == "" {
dom = globalConfig.domain
@@ -96,31 +107,46 @@ func GetDomain() string {
func SetDomain(dom string) {
globalConfig.Lock()
globalConfig.domain = dom
if globalConfig.storage != nil {
globalConfig.storage.SetDomain(dom)
if globalConfig.locales != nil {
for _, locale := range globalConfig.locales {
locale.SetDomain(dom)
}
}
globalConfig.Unlock()
loadStorage(true)
loadLocales(true)
}
// GetLanguage is the language getter for the package configuration
// GetLanguage returns the language gotext will translate into.
// If multiple languages have been supplied, the first one will be returned.
// If no language has been supplied, the fallback will be returned.
func GetLanguage() string {
globalConfig.RLock()
lang := globalConfig.language
globalConfig.RUnlock()
return lang
languages := GetLanguages()
if len(languages) == 0 {
return FallbackLocale
}
return languages[0]
}
// SetLanguage sets the language code to be used at package level.
// GetLanguages returns all languages that have been supplied.
func GetLanguages() []string {
globalConfig.RLock()
defer globalConfig.RUnlock()
return globalConfig.languages
}
// SetLanguage sets the language code (or colon separated language codes) to be used at package level.
// It reloads the corresponding Translation file.
func SetLanguage(lang string) {
globalConfig.Lock()
globalConfig.language = SimplifiedLocale(lang)
var languages []string
for _, language := range strings.Split(lang, ":") {
languages = append(languages, SimplifiedLocale(language))
}
globalConfig.languages = languages
globalConfig.Unlock()
loadStorage(true)
loadLocales(true)
}
// GetLibrary is the library getter for the package configuration
@@ -132,14 +158,56 @@ func GetLibrary() string {
return lib
}
// SetLibrary sets the root path for the loale directories and files to be used at package level.
// SetLibrary sets the root path for the locale directories and files to be used at package level.
// It reloads the corresponding Translation file.
func SetLibrary(lib string) {
globalConfig.Lock()
globalConfig.library = lib
globalConfig.Unlock()
loadStorage(true)
loadLocales(true)
}
func GetLocales() []*Locale {
globalConfig.RLock()
defer globalConfig.RUnlock()
return globalConfig.locales
}
// GetStorage is the locale storage getter for the package configuration.
//
// Deprecated: Storage has been renamed to Locale for consistency, use GetLocales instead.
func GetStorage() *Locale {
locales := GetLocales()
if len(locales) > 0 {
return locales[0]
}
return nil
}
// SetLocales allows for overriding the global Locale objects with ones built manually with
// NewLocale(). This makes it possible to attach custom Domain objects from in-memory po/mo.
// The library, language and domain of the first Locale will set the default global configuration.
func SetLocales(locales []*Locale) {
globalConfig.Lock()
defer globalConfig.Unlock()
globalConfig.locales = locales
globalConfig.library = locales[0].path
globalConfig.domain = locales[0].defaultDomain
var languages []string
for _, locale := range locales {
languages = append(languages, locale.lang)
}
globalConfig.languages = languages
}
// SetStorage allows overriding the global Locale object with one built manually with NewLocale().
//
// Deprecated: Storage has been renamed to Locale for consistency, use SetLocales instead.
func SetStorage(locale *Locale) {
SetLocales([]*Locale{locale})
}
// Configure sets all configuration variables to be used at package level and reloads the corresponding Translation file.
@@ -149,11 +217,15 @@ func SetLibrary(lib string) {
func Configure(lib, lang, dom string) {
globalConfig.Lock()
globalConfig.library = lib
globalConfig.language = SimplifiedLocale(lang)
var languages []string
for _, language := range strings.Split(lang, ":") {
languages = append(languages, SimplifiedLocale(language))
}
globalConfig.languages = languages
globalConfig.domain = dom
globalConfig.Unlock()
loadStorage(true)
loadLocales(true)
}
// Get uses the default domain globally set to return the corresponding Translation of a given string.
@@ -171,38 +243,46 @@ func GetN(str, plural string, n int, vars ...interface{}) string {
// GetD returns the corresponding Translation in the given domain for a given string.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func GetD(dom, str string, vars ...interface{}) string {
// Try to load default package Locale storage
loadStorage(false)
// Try to load default package Locales
loadLocales(false)
// Return Translation
globalConfig.RLock()
defer globalConfig.RUnlock()
if _, ok := globalConfig.storage.Domains[dom]; !ok {
globalConfig.storage.AddDomain(dom)
var tr string
for i, locale := range globalConfig.locales {
if _, ok := locale.Domains[dom]; !ok {
locale.AddDomain(dom)
}
if !locale.IsTranslatedD(dom, str) && i < (len(globalConfig.locales)-1) {
continue
}
tr = locale.GetD(dom, str, vars...)
break
}
tr := globalConfig.storage.GetD(dom, str, vars...)
globalConfig.RUnlock()
return tr
}
// GetND retrieves the (N)th plural form of Translation in the given domain for a given string.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func GetND(dom, str, plural string, n int, vars ...interface{}) string {
// Try to load default package Locale storage
loadStorage(false)
// Try to load default package Locales
loadLocales(false)
// Return Translation
globalConfig.RLock()
defer globalConfig.RUnlock()
if _, ok := globalConfig.storage.Domains[dom]; !ok {
globalConfig.storage.AddDomain(dom)
var tr string
for i, locale := range globalConfig.locales {
if _, ok := locale.Domains[dom]; !ok {
locale.AddDomain(dom)
}
if !locale.IsTranslatedND(dom, str, n) && i < (len(globalConfig.locales)-1) {
continue
}
tr = locale.GetND(dom, str, plural, n, vars...)
break
}
tr := globalConfig.storage.GetND(dom, str, plural, n, vars...)
globalConfig.RUnlock()
return tr
}
@@ -221,27 +301,126 @@ func GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
// GetDC returns the corresponding Translation in the given domain for the given string in the given context.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func GetDC(dom, str, ctx string, vars ...interface{}) string {
// Try to load default package Locale storage
loadStorage(false)
// Try to load default package Locales
loadLocales(false)
// Return Translation
globalConfig.RLock()
tr := globalConfig.storage.GetDC(dom, str, ctx, vars...)
globalConfig.RUnlock()
defer globalConfig.RUnlock()
var tr string
for i, locale := range globalConfig.locales {
if !locale.IsTranslatedDC(dom, str, ctx) && i < (len(globalConfig.locales)-1) {
continue
}
tr = locale.GetDC(dom, str, ctx, vars...)
break
}
return tr
}
// GetNDC retrieves the (N)th plural form of Translation in the given domain for a given string.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func GetNDC(dom, str, plural string, n int, ctx string, vars ...interface{}) string {
// Try to load default package Locale storage
loadStorage(false)
// Try to load default package Locales
loadLocales(false)
// Return Translation
globalConfig.RLock()
tr := globalConfig.storage.GetNDC(dom, str, plural, n, ctx, vars...)
globalConfig.RUnlock()
defer globalConfig.RUnlock()
var tr string
for i, locale := range globalConfig.locales {
if !locale.IsTranslatedNDC(dom, str, n, ctx) && i < (len(globalConfig.locales)-1) {
continue
}
tr = locale.GetNDC(dom, str, plural, n, ctx, vars...)
break
}
return tr
}
// IsTranslated reports whether a string is translated in given languages.
// When the langs argument is omitted, the output of GetLanguages is used.
func IsTranslated(str string, langs ...string) bool {
return IsTranslatedND(GetDomain(), str, 0, langs...)
}
// IsTranslatedN reports whether a plural string is translated in given languages.
// When the langs argument is omitted, the output of GetLanguages is used.
func IsTranslatedN(str string, n int, langs ...string) bool {
return IsTranslatedND(GetDomain(), str, n, langs...)
}
// IsTranslatedD reports whether a domain string is translated in given languages.
// When the langs argument is omitted, the output of GetLanguages is used.
func IsTranslatedD(dom, str string, langs ...string) bool {
return IsTranslatedND(dom, str, 0, langs...)
}
// IsTranslatedND reports whether a plural domain string is translated in any of given languages.
// When the langs argument is omitted, the output of GetLanguages is used.
func IsTranslatedND(dom, str string, n int, langs ...string) bool {
if len(langs) == 0 {
langs = GetLanguages()
}
loadLocales(false)
globalConfig.RLock()
defer globalConfig.RUnlock()
for _, lang := range langs {
lang = SimplifiedLocale(lang)
for _, supportedLocale := range globalConfig.locales {
if lang != supportedLocale.GetActualLanguage(dom) {
continue
}
return supportedLocale.IsTranslatedND(dom, str, n)
}
}
return false
}
// IsTranslatedC reports whether a context string is translated in given languages.
// When the langs argument is omitted, the output of GetLanguages is used.
func IsTranslatedC(str, ctx string, langs ...string) bool {
return IsTranslatedNDC(GetDomain(), str, 0, ctx, langs...)
}
// IsTranslatedNC reports whether a plural context string is translated in given languages.
// When the langs argument is omitted, the output of GetLanguages is used.
func IsTranslatedNC(str string, n int, ctx string, langs ...string) bool {
return IsTranslatedNDC(GetDomain(), str, n, ctx, langs...)
}
// IsTranslatedDC reports whether a domain context string is translated in given languages.
// When the langs argument is omitted, the output of GetLanguages is used.
func IsTranslatedDC(dom, str, ctx string, langs ...string) bool {
return IsTranslatedNDC(dom, str, 0, ctx, langs...)
}
// IsTranslatedNDC reports whether a plural domain context string is translated in any of given languages.
// When the langs argument is omitted, the output of GetLanguages is used.
func IsTranslatedNDC(dom, str string, n int, ctx string, langs ...string) bool {
if len(langs) == 0 {
langs = GetLanguages()
}
loadLocales(false)
globalConfig.RLock()
defer globalConfig.RUnlock()
for _, lang := range langs {
lang = SimplifiedLocale(lang)
for _, locale := range globalConfig.locales {
if lang != locale.GetActualLanguage(dom) {
continue
}
return locale.IsTranslatedNDC(dom, str, n, ctx)
}
}
return false
}

View File

@@ -0,0 +1,25 @@
package gotext
// IsTranslatedIntrospector is able to determine whether a given string is translated.
// Examples of this introspector are Po and Mo, which are specific to their domain.
// Locale holds multiple domains and also implements IsTranslatedDomainIntrospector.
type IsTranslatedIntrospector interface {
IsTranslated(str string) bool
IsTranslatedN(str string, n int) bool
IsTranslatedC(str, ctx string) bool
IsTranslatedNC(str string, n int, ctx string) bool
}
// IsTranslatedDomainIntrospector is able to determine whether a given string is translated.
// Example of this introspector is Locale, which holds multiple domains.
// Simpler objects that are domain-specific, like Po or Mo, implement IsTranslatedIntrospector.
type IsTranslatedDomainIntrospector interface {
IsTranslated(str string) bool
IsTranslatedN(str string, n int) bool
IsTranslatedD(dom, str string) bool
IsTranslatedND(dom, str string, n int) bool
IsTranslatedC(str, ctx string) bool
IsTranslatedNC(str string, n int, ctx string) bool
IsTranslatedDC(dom, str, ctx string) bool
IsTranslatedNDC(dom, str string, n int, ctx string) bool
}

View File

@@ -111,15 +111,44 @@ func (l *Locale) findExt(dom, ext string) string {
return ""
}
// GetActualLanguage inspects the filesystem and decides whether to strip
// a CC part of the ll_CC locale string.
func (l *Locale) GetActualLanguage(dom string) string {
extensions := []string{"mo", "po"}
var fp string
for _, ext := range extensions {
// 'll' (or 'll_CC') exists, and it was specified as-is
fp = path.Join(l.path, l.lang, "LC_MESSAGES", dom+"."+ext)
if l.fileExists(fp) {
return l.lang
}
// 'll' exists, but 'll_CC' was specified
if len(l.lang) > 2 {
fp = path.Join(l.path, l.lang[:2], "LC_MESSAGES", dom+"."+ext)
if l.fileExists(fp) {
return l.lang[:2]
}
}
// 'll' (or 'll_CC') exists outside of LC_category, and it was specified as-is
fp = path.Join(l.path, l.lang, dom+"."+ext)
if l.fileExists(fp) {
return l.lang
}
// 'll' exists outside of LC_category, but 'll_CC' was specified
if len(l.lang) > 2 {
fp = path.Join(l.path, l.lang[:2], dom+"."+ext)
if l.fileExists(fp) {
return l.lang[:2]
}
}
}
return ""
}
func (l *Locale) fileExists(filename string) bool {
if l.fs != nil {
f, err := l.fs.Open(filename)
if err != nil {
return false
}
_, err = f.Stat()
_, err := fs.Stat(l.fs, filename)
return err == nil
}
_, err := os.Stat(filename)
return err == nil
@@ -192,6 +221,14 @@ func (l *Locale) SetDomain(dom string) {
l.Unlock()
}
// GetLanguage is the lang getter for Locale configuration
func (l *Locale) GetLanguage() string {
l.RLock()
lang := l.lang
l.RUnlock()
return lang
}
// Get uses a domain "default" to return the corresponding Translation of a given string.
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
func (l *Locale) Get(str string, vars ...interface{}) string {
@@ -311,6 +348,66 @@ func (l *Locale) GetTranslations() map[string]*Translation {
return all
}
// IsTranslated reports whether a string is translated
func (l *Locale) IsTranslated(str string) bool {
return l.IsTranslatedND(l.GetDomain(), str, 0)
}
// IsTranslatedN reports whether a plural string is translated
func (l *Locale) IsTranslatedN(str string, n int) bool {
return l.IsTranslatedND(l.GetDomain(), str, n)
}
// IsTranslatedD reports whether a domain string is translated
func (l *Locale) IsTranslatedD(dom, str string) bool {
return l.IsTranslatedND(dom, str, 0)
}
// IsTranslatedND reports whether a plural domain string is translated
func (l *Locale) IsTranslatedND(dom, str string, n int) bool {
l.RLock()
defer l.RUnlock()
if l.Domains == nil {
return false
}
translator, ok := l.Domains[dom]
if !ok {
return false
}
return translator.GetDomain().IsTranslatedN(str, n)
}
// IsTranslatedC reports whether a context string is translated
func (l *Locale) IsTranslatedC(str, ctx string) bool {
return l.IsTranslatedNDC(l.GetDomain(), str, 0, ctx)
}
// IsTranslatedNC reports whether a plural context string is translated
func (l *Locale) IsTranslatedNC(str string, n int, ctx string) bool {
return l.IsTranslatedNDC(l.GetDomain(), str, n, ctx)
}
// IsTranslatedDC reports whether a domain context string is translated
func (l *Locale) IsTranslatedDC(dom, str, ctx string) bool {
return l.IsTranslatedNDC(dom, str, 0, ctx)
}
// IsTranslatedNDC reports whether a plural domain context string is translated
func (l *Locale) IsTranslatedNDC(dom string, str string, n int, ctx string) bool {
l.RLock()
defer l.RUnlock()
if l.Domains == nil {
return false
}
translator, ok := l.Domains[dom]
if !ok {
return false
}
return translator.GetDomain().IsTranslatedNC(str, n, ctx)
}
// LocaleEncoding is used as intermediary storage to encode Locale objects to Gob.
type LocaleEncoding struct {
Path string

View File

@@ -92,6 +92,19 @@ func (mo *Mo) GetNC(str, plural string, n int, ctx string, vars ...interface{})
return mo.domain.GetNC(str, plural, n, ctx, vars...)
}
func (mo *Mo) IsTranslated(str string) bool {
return mo.domain.IsTranslated(str)
}
func (mo *Mo) IsTranslatedN(str string, n int) bool {
return mo.domain.IsTranslatedN(str, n)
}
func (mo *Mo) IsTranslatedC(str, ctx string) bool {
return mo.domain.IsTranslatedC(str, ctx)
}
func (mo *Mo) IsTranslatedNC(str string, n int, ctx string) bool {
return mo.domain.IsTranslatedNC(str, n, ctx)
}
func (mo *Mo) MarshalBinary() ([]byte, error) {
return mo.domain.MarshalBinary()
}
@@ -263,10 +276,10 @@ func (mo *Mo) addTranslation(msgid, msgstr []byte) {
if len(msgctxt) > 0 {
// With context...
if _, ok := mo.domain.contexts[string(msgctxt)]; !ok {
mo.domain.contexts[string(msgctxt)] = make(map[string]*Translation)
if _, ok := mo.domain.contextTranslations[string(msgctxt)]; !ok {
mo.domain.contextTranslations[string(msgctxt)] = make(map[string]*Translation)
}
mo.domain.contexts[string(msgctxt)][translation.ID] = translation
mo.domain.contextTranslations[string(msgctxt)][translation.ID] = translation
} else {
mo.domain.translations[translation.ID] = translation
}

View File

@@ -25,7 +25,7 @@ Example:
func main() {
// Create po object
po := gotext.NewPoTranslator()
po := gotext.NewPo()
// Parse .po file
po.ParseFile("/path/to/po/file/translations.po")
@@ -114,6 +114,19 @@ func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{})
return po.domain.GetNC(str, plural, n, ctx, vars...)
}
func (po *Po) IsTranslated(str string) bool {
return po.domain.IsTranslated(str)
}
func (po *Po) IsTranslatedN(str string, n int) bool {
return po.domain.IsTranslatedN(str, n)
}
func (po *Po) IsTranslatedC(str, ctx string) bool {
return po.domain.IsTranslatedC(str, ctx)
}
func (po *Po) IsTranslatedNC(str string, n int, ctx string) bool {
return po.domain.IsTranslatedNC(str, n, ctx)
}
func (po *Po) MarshalText() ([]byte, error) {
return po.domain.MarshalText()
}
@@ -223,10 +236,10 @@ func (po *Po) saveBuffer() {
po.domain.translations[po.domain.trBuffer.ID] = po.domain.trBuffer
} else {
// With context...
if _, ok := po.domain.contexts[po.domain.ctxBuffer]; !ok {
po.domain.contexts[po.domain.ctxBuffer] = make(map[string]*Translation)
if _, ok := po.domain.contextTranslations[po.domain.ctxBuffer]; !ok {
po.domain.contextTranslations[po.domain.ctxBuffer] = make(map[string]*Translation)
}
po.domain.contexts[po.domain.ctxBuffer][po.domain.trBuffer.ID] = po.domain.trBuffer
po.domain.contextTranslations[po.domain.ctxBuffer][po.domain.trBuffer.ID] = po.domain.trBuffer
// Cleanup current context buffer if needed
if po.domain.trBuffer.ID != "" {

View File

@@ -78,3 +78,15 @@ func (t *Translation) GetN(n int) string {
// Return untranslated plural by default
return t.PluralID
}
// IsTranslated reports whether a string is translated
func (t *Translation) IsTranslated() bool {
tr, ok := t.Trs[0]
return tr != "" && ok
}
// IsTranslatedN reports whether a plural string is translated
func (t *Translation) IsTranslatedN(n int) bool {
tr, ok := t.Trs[n]
return tr != "" && ok
}

View File

@@ -61,7 +61,7 @@ func (te *TranslatorEncoding) GetTranslator() Translator {
po.domain.nplurals = te.Nplurals
po.domain.plural = te.Plural
po.domain.translations = te.Translations
po.domain.contexts = te.Contexts
po.domain.contextTranslations = te.Contexts
return po
}

2
vendor/modules.txt vendored
View File

@@ -1256,7 +1256,7 @@ github.com/klauspost/cpuid/v2
## explicit; go 1.18
github.com/leodido/go-urn
github.com/leodido/go-urn/scim/schema
# github.com/leonelquinteros/gotext v1.5.3-0.20230317130943-71a59c05b2c1
# github.com/leonelquinteros/gotext v1.6.0
## explicit; go 1.13
github.com/leonelquinteros/gotext
github.com/leonelquinteros/gotext/plurals