Compare commits
16 Commits
fix/574
...
weblate-fi
Author | SHA1 | Date | |
---|---|---|---|
f282cadccd | |||
932c50ede3 | |||
cdf3f7d638 | |||
57b4b8e509 | |||
092ae6e700 | |||
436a938373 | |||
41cb05ccbd | |||
f53911f442 | |||
05788e44e0 | |||
e4e5f4f1f6 | |||
937656a9fa | |||
274c3a64c9 | |||
d4d0ddf74a | |||
bc948988a1 | |||
3a229d9dc9 | |||
d44b18d7be
|
28
.drone.yml
28
.drone.yml
@ -18,6 +18,32 @@ steps:
|
||||
depends_on:
|
||||
- make check
|
||||
|
||||
- name: find updated translatable strings
|
||||
image: git.coopcloud.tech/toolshed/drone-xgotext
|
||||
depends_on:
|
||||
- make check
|
||||
settings:
|
||||
exclude: "vendor,.git,.bats"
|
||||
when:
|
||||
event: [push]
|
||||
|
||||
- name: commit catalogue template changes
|
||||
image: debian:bookworm
|
||||
environment:
|
||||
SSH_KEY:
|
||||
from_secret: abra_bot_deploy_key
|
||||
GIT_SSH_COMMAND: "ssh -o 'PubkeyAcceptedKeyTypes +ssh-rsa'"
|
||||
commands:
|
||||
- apt update && DEBIAN_FRONTEND=noninteractive apt install -y git openssh-client
|
||||
- mkdir $HOME/.ssh/
|
||||
- eval `ssh-agent`
|
||||
- echo "$SSH_KEY" | ssh-add -
|
||||
- ssh-keyscan -p 2222 -t rsa git.coopcloud.tech >> $HOME/.ssh/known_hosts
|
||||
- chmod -R go-rwx $HOME/.ssh
|
||||
- "git commit -a -m 'Chore: regenerate gettext catalogue template' -m '[ci skip]' && git push -u origin $DRONE_COMMIT_BRANCH"
|
||||
depends_on:
|
||||
- find updated translatable strings
|
||||
|
||||
- name: fetch
|
||||
image: docker:git
|
||||
commands:
|
||||
@ -26,7 +52,7 @@ steps:
|
||||
- make check
|
||||
- make test
|
||||
when:
|
||||
event: tag
|
||||
event: [tag]
|
||||
|
||||
- name: release
|
||||
image: goreleaser/goreleaser:v2.5.1
|
||||
|
@ -1,11 +1,12 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var AppCommand = &cobra.Command{
|
||||
Use: "app [cmd] [args] [flags]",
|
||||
Aliases: []string{"a"},
|
||||
Short: "Manage apps",
|
||||
Short: gotext.Get("Manage apps"),
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/log"
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -41,7 +42,7 @@ type serverStatus struct {
|
||||
var AppListCommand = &cobra.Command{
|
||||
Use: "list [flags]",
|
||||
Aliases: []string{"ls"},
|
||||
Short: "List all managed apps",
|
||||
Short: gotext.Get("List all managed apps"),
|
||||
Long: `Generate a report of all managed apps.
|
||||
|
||||
Use "--status/-S" flag to query all servers for the live deployment status.`,
|
||||
|
@ -21,12 +21,14 @@ import (
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
var AppPsCommand = &cobra.Command{
|
||||
Use: "ps <domain> [flags]",
|
||||
Aliases: []string{"p"},
|
||||
Short: "Check app deployment status",
|
||||
Short: gotext.Get("Check app deployment status"),
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: func(
|
||||
cmd *cobra.Command,
|
||||
|
5
go.mod
5
go.mod
@ -1,8 +1,8 @@
|
||||
module coopcloud.tech/abra
|
||||
|
||||
go 1.23.0
|
||||
go 1.23.5
|
||||
|
||||
toolchain go1.23.1
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb
|
||||
@ -17,6 +17,7 @@ require (
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/go-git/go-git/v5 v5.14.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/leonelquinteros/gotext v1.7.2
|
||||
github.com/moby/sys/signal v0.7.1
|
||||
github.com/moby/term v0.5.2
|
||||
github.com/pkg/errors v0.9.1
|
||||
|
2
go.sum
2
go.sum
@ -611,6 +611,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+YP7G1Mc=
|
||||
github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8=
|
||||
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
|
20
locales/default.pot
Normal file
20
locales/default.pot
Normal file
@ -0,0 +1,20 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: \n"
|
||||
"X-Generator: xgotext\n"
|
||||
|
||||
#: cli/app/ps.go:31
|
||||
msgid "Check app deployment status"
|
||||
msgstr ""
|
||||
|
||||
#: cli/app/list.go:45
|
||||
msgid "List all managed apps"
|
||||
msgstr ""
|
||||
|
||||
#: cli/app/app.go:11
|
||||
msgid "Manage apps"
|
||||
msgstr ""
|
30
locales/es/LC_MESSAGES/default.po
Normal file
30
locales/es/LC_MESSAGES/default.po
Normal file
@ -0,0 +1,30 @@
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-08-04 14:15+0000\n"
|
||||
"PO-Revision-Date: 2025-08-04 14:15+0000\n"
|
||||
"Last-Translator: 3wordchant <3wc.coopcloud@doesthisthing.work>\n"
|
||||
"Language-Team: Spanish <https://translate.coopcloud.tech/projects/co-op-"
|
||||
"cloud/abra/es/>\n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: ENCODING\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.12.2\n"
|
||||
|
||||
#: cli/app/ps.go:31
|
||||
msgid "Check app deployment status"
|
||||
msgstr ""
|
||||
|
||||
#: cli/app/list.go:45
|
||||
#, fuzzy
|
||||
#| msgid "Manage apps"
|
||||
msgid "List all managed apps"
|
||||
msgstr "Gestionar aplicaciones"
|
||||
|
||||
#: cli/app/app.go:11
|
||||
msgid "Manage apps"
|
||||
msgstr "Gestionar aplicaciones"
|
30
pkg/lang/lang.go
Normal file
30
pkg/lang/lang.go
Normal file
@ -0,0 +1,30 @@
|
||||
package lang
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetLocale() string {
|
||||
if loc := os.Getenv("LC_MESSAGES"); loc != "" {
|
||||
return NormalizeLocale(loc)
|
||||
}
|
||||
|
||||
if loc := os.Getenv("LANG"); loc != "" {
|
||||
return NormalizeLocale(loc)
|
||||
}
|
||||
|
||||
return "C.UTF-8"
|
||||
}
|
||||
|
||||
func NormalizeLocale(loc string) string {
|
||||
if idx := strings.Index(loc, "."); idx != -1 {
|
||||
return loc[:idx]
|
||||
}
|
||||
|
||||
if idx := strings.Index(loc, "@"); idx != -1 {
|
||||
return loc[:idx]
|
||||
}
|
||||
|
||||
return loc
|
||||
}
|
32
vendor/github.com/leonelquinteros/gotext/.gitignore
generated
vendored
Normal file
32
vendor/github.com/leonelquinteros/gotext/.gitignore
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# Local IDE
|
||||
.project
|
||||
.settings
|
||||
.buildpath
|
||||
.idea
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
cli/xgotext/fixtures/out
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
*.DS_Store
|
46
vendor/github.com/leonelquinteros/gotext/CODE_OF_CONDUCT.md
generated
vendored
Normal file
46
vendor/github.com/leonelquinteros/gotext/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at leonel.quinteros@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
19
vendor/github.com/leonelquinteros/gotext/CONTRIBUTING.md
generated
vendored
Normal file
19
vendor/github.com/leonelquinteros/gotext/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
# CONTRIBUTING
|
||||
|
||||
This open source project welcomes everybody that wants to contribute to it by implementing new features, fixing bugs, testing, creating documentation or simply talk about it.
|
||||
|
||||
Most contributions will start by creating a new Issue to discuss what is the contribution about and to agree on the steps to move forward.
|
||||
|
||||
## Issues
|
||||
|
||||
All issues reports are welcome. Open a new Issue whenever you want to report a bug, request a change or make a proposal.
|
||||
|
||||
This should be your start point of contribution.
|
||||
|
||||
|
||||
## Pull Requests
|
||||
|
||||
If you have any changes that can be merged, feel free to send a Pull Request.
|
||||
|
||||
Usually, you'd want to create a new Issue to discuss about the change you want to merge and why it's needed or what it solves.
|
||||
|
55
vendor/github.com/leonelquinteros/gotext/LICENSE
generated
vendored
Normal file
55
vendor/github.com/leonelquinteros/gotext/LICENSE
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Leonel Quinteros
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
Package `plurals`
|
||||
|
||||
Original:
|
||||
https://github.com/ojii/gettext.go/tree/b6dae1d7af8a8441285e42661565760b530a8a57/pluralforms
|
||||
|
||||
License:
|
||||
https://raw.githubusercontent.com/ojii/gettext.go/b6dae1d7af8a8441285e42661565760b530a8a57/LICENSE
|
||||
|
||||
Copyright (c) 2016, Jonas Obrist
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Jonas Obrist nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL JONAS OBRIST BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
266
vendor/github.com/leonelquinteros/gotext/README.md
generated
vendored
Normal file
266
vendor/github.com/leonelquinteros/gotext/README.md
generated
vendored
Normal file
@ -0,0 +1,266 @@
|
||||
[](https://github.com/leonelquinteros/gotext)
|
||||
[](LICENSE)
|
||||

|
||||
[](https://goreportcard.com/report/github.com/leonelquinteros/gotext)
|
||||
[](https://pkg.go.dev/github.com/leonelquinteros/gotext)
|
||||
|
||||
|
||||
# Gotext
|
||||
|
||||
[GNU gettext utilities](https://www.gnu.org/software/gettext) for Go.
|
||||
|
||||
|
||||
# Features
|
||||
|
||||
- Implements GNU gettext support in native Go.
|
||||
- Complete support for [PO files](https://www.gnu.org/software/gettext/manual/html_node/PO-Files.html) including:
|
||||
- Support for multiline strings and headers.
|
||||
- Support for variables inside translation strings using Go's [fmt syntax](https://golang.org/pkg/fmt/).
|
||||
- Support for [pluralization rules](https://www.gnu.org/software/gettext/manual/html_node/Translating-plural-forms.html).
|
||||
- Support for [message contexts](https://www.gnu.org/software/gettext/manual/html_node/Contexts.html).
|
||||
- Support for MO files.
|
||||
- Thread-safe: This package is safe for concurrent use across multiple goroutines.
|
||||
- It works with UTF-8 encoding as it's the default for Go language.
|
||||
- Unit tests available.
|
||||
- Language codes are automatically simplified from the form `en_UK` to `en` if the first isn't available.
|
||||
- Ready to use inside Go templates.
|
||||
- Objects are serializable to []byte to store them in cache.
|
||||
- Support for Go Modules.
|
||||
|
||||
|
||||
# License
|
||||
|
||||
[MIT license](LICENSE)
|
||||
|
||||
|
||||
# Documentation
|
||||
|
||||
Refer to package documentation at (https://pkg.go.dev/github.com/leonelquinteros/gotext)
|
||||
|
||||
|
||||
# Installation
|
||||
|
||||
```
|
||||
go get github.com/leonelquinteros/gotext
|
||||
```
|
||||
|
||||
- There are no requirements or dependencies to use this package.
|
||||
- No need to install GNU gettext utilities (unless specific needs of CLI tools).
|
||||
- No need for environment variables. Some naming conventions are applied but not needed.
|
||||
|
||||
|
||||
# Usage examples
|
||||
|
||||
## Using package for single language/domain settings
|
||||
|
||||
For quick/simple translations you can use the package level functions directly.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
|
||||
// Translate text from default domain
|
||||
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
|
||||
|
||||
// Translate text from a different domain without reconfigure
|
||||
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Using dynamic variables on translations
|
||||
|
||||
All translation strings support dynamic variables to be inserted without translate.
|
||||
Use the fmt.Printf syntax (from Go's "fmt" package) to specify how to print the non-translated variable inside the translation string.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
|
||||
// Set variables
|
||||
name := "John"
|
||||
|
||||
// Translate text with variables
|
||||
fmt.Println(gotext.Get("Hi, my name is %s", name))
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Using Locale object
|
||||
|
||||
When having multiple languages/domains/libraries at the same time, you can create Locale objects for each variation
|
||||
so you can handle each settings on their own.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create Locale with library path and language code
|
||||
l := gotext.NewLocale("/path/to/locales/root/dir", "es_UY")
|
||||
|
||||
// Load domain '/path/to/locales/root/dir/es_UY/default.po'
|
||||
l.AddDomain("default")
|
||||
|
||||
// Translate text from default domain
|
||||
fmt.Println(l.Get("Translate this"))
|
||||
|
||||
// Load different domain
|
||||
l.AddDomain("translations")
|
||||
|
||||
// Translate text from domain
|
||||
fmt.Println(l.GetD("translations", "Translate this"))
|
||||
}
|
||||
```
|
||||
|
||||
This is also helpful for using inside templates (from the "text/template" package), where you can pass the Locale object to the template.
|
||||
If you set the Locale object as "Loc" in the template, then the template code would look like:
|
||||
|
||||
```
|
||||
{{ .Loc.Get "Translate this" }}
|
||||
```
|
||||
|
||||
|
||||
## Using the Po object to handle .po files and PO-formatted strings
|
||||
|
||||
For when you need to work with PO files and strings,
|
||||
you can directly use the Po object to parse it and access the translations in there in the same way.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Set PO content
|
||||
str := `
|
||||
msgid "Translate this"
|
||||
msgstr "Translated text"
|
||||
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgstr "This one sets the var: %s"
|
||||
`
|
||||
|
||||
// Create Po object
|
||||
po := gotext.NewPo()
|
||||
po.Parse(str)
|
||||
|
||||
fmt.Println(po.Get("Translate this"))
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Use plural forms of translations
|
||||
|
||||
PO format supports defining one or more plural forms for the same translation.
|
||||
Relying on the PO file headers, a Plural-Forms formula can be set on the translation file
|
||||
as defined in (https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/Plural-forms.html)
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Set PO content
|
||||
str := `
|
||||
msgid ""
|
||||
msgstr ""
|
||||
|
||||
# Header below
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
msgid "Translate this"
|
||||
msgstr "Translated text"
|
||||
|
||||
msgid "Another string"
|
||||
msgstr ""
|
||||
|
||||
msgid "One with var: %s"
|
||||
msgid_plural "Several with vars: %s"
|
||||
msgstr[0] "This one is the singular: %s"
|
||||
msgstr[1] "This one is the plural: %s"
|
||||
`
|
||||
|
||||
// Create Po object
|
||||
po := new(gotext.Po)
|
||||
po.Parse(str)
|
||||
|
||||
fmt.Println(po.GetN("One with var: %s", "Several with vars: %s", 54, v))
|
||||
// "This one is the plural: Variable"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
# Locales directories structure
|
||||
|
||||
The package will assume a directories structure starting with a base path that will be provided to the package configuration
|
||||
or to object constructors depending on the use, but either will use the same convention to lookup inside the base path.
|
||||
|
||||
Inside the base directory where will be the language directories named using the language and country 2-letter codes (en_US, es_AR, ...).
|
||||
All package functions can lookup after the simplified version for each language in case the full code isn't present but the more general language code exists.
|
||||
So if the language set is `en_UK`, but there is no directory named after that code and there is a directory named `en`,
|
||||
all package functions will be able to resolve this generalization and provide translations for the more general library.
|
||||
|
||||
The language codes are assumed to be [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) codes (2-letter codes).
|
||||
That said, most functions will work with any coding standard as long the directory name matches the language code set on the configuration.
|
||||
|
||||
Then, there can be a `LC_MESSAGES` containing all PO files or the PO files themselves.
|
||||
A library directory structure can look like:
|
||||
|
||||
```
|
||||
/path/to/locales
|
||||
/path/to/locales/en_US
|
||||
/path/to/locales/en_US/LC_MESSAGES
|
||||
/path/to/locales/en_US/LC_MESSAGES/default.po
|
||||
/path/to/locales/en_US/LC_MESSAGES/extras.po
|
||||
/path/to/locales/en_UK
|
||||
/path/to/locales/en_UK/LC_MESSAGES
|
||||
/path/to/locales/en_UK/LC_MESSAGES/default.po
|
||||
/path/to/locales/en_UK/LC_MESSAGES/extras.po
|
||||
/path/to/locales/en_AU
|
||||
/path/to/locales/en_AU/LC_MESSAGES
|
||||
/path/to/locales/en_AU/LC_MESSAGES/default.po
|
||||
/path/to/locales/en_AU/LC_MESSAGES/extras.po
|
||||
/path/to/locales/es
|
||||
/path/to/locales/es/default.po
|
||||
/path/to/locales/es/extras.po
|
||||
/path/to/locales/es_ES
|
||||
/path/to/locales/es_ES/default.po
|
||||
/path/to/locales/es_ES/extras.po
|
||||
/path/to/locales/fr
|
||||
/path/to/locales/fr/default.po
|
||||
/path/to/locales/fr/extras.po
|
||||
```
|
||||
|
||||
And so on...
|
||||
|
||||
|
||||
# Contribute
|
||||
|
||||
- Please, contribute.
|
||||
- Use the package on your projects.
|
||||
- Report issues on Github.
|
||||
- Send pull requests for bugfixes and improvements.
|
||||
- Send proposals on Github issues.
|
||||
|
866
vendor/github.com/leonelquinteros/gotext/domain.go
generated
vendored
Normal file
866
vendor/github.com/leonelquinteros/gotext/domain.go
generated
vendored
Normal file
@ -0,0 +1,866 @@
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/leonelquinteros/gotext/plurals"
|
||||
)
|
||||
|
||||
// Domain has all the common functions for dealing with a gettext domain
|
||||
// it's initialized with a GettextFile (which represents either a Po or Mo file)
|
||||
type Domain struct {
|
||||
Headers HeaderMap
|
||||
|
||||
// Language header
|
||||
Language string
|
||||
|
||||
// Plural-Forms header
|
||||
PluralForms string
|
||||
|
||||
// Preserve comments at head of PO for round-trip
|
||||
headerComments []string
|
||||
|
||||
// Parsed Plural-Forms header values
|
||||
nplurals int
|
||||
plural string
|
||||
pluralforms plurals.Expression
|
||||
|
||||
// Storage
|
||||
translations map[string]*Translation
|
||||
contextTranslations map[string]map[string]*Translation
|
||||
pluralTranslations map[string]*Translation
|
||||
|
||||
// Sync Mutex
|
||||
trMutex sync.RWMutex
|
||||
pluralMutex sync.RWMutex
|
||||
|
||||
// Parsing buffers
|
||||
trBuffer *Translation
|
||||
ctxBuffer string
|
||||
refBuffer string
|
||||
|
||||
customPluralResolver func(int) int
|
||||
}
|
||||
|
||||
// HeaderMap preserves MIMEHeader behaviour, without the canonicalisation
|
||||
type HeaderMap map[string][]string
|
||||
|
||||
// Add key/value pair to HeaderMap
|
||||
func (m HeaderMap) Add(key, value string) {
|
||||
m[key] = append(m[key], value)
|
||||
}
|
||||
|
||||
// Del key from HeaderMap
|
||||
func (m HeaderMap) Del(key string) {
|
||||
delete(m, key)
|
||||
}
|
||||
|
||||
// Get value for key from HeaderMap
|
||||
func (m HeaderMap) Get(key string) string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
v := m[key]
|
||||
if len(v) == 0 {
|
||||
return ""
|
||||
}
|
||||
return v[0]
|
||||
}
|
||||
|
||||
// Set key/value pair in HeaderMap
|
||||
func (m HeaderMap) Set(key, value string) {
|
||||
m[key] = []string{value}
|
||||
}
|
||||
|
||||
// Values returns all values for a given key from HeaderMap
|
||||
func (m HeaderMap) Values(key string) []string {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return m[key]
|
||||
}
|
||||
|
||||
// NewDomain creates a new Domain instance
|
||||
func NewDomain() *Domain {
|
||||
domain := new(Domain)
|
||||
|
||||
domain.Headers = make(HeaderMap)
|
||||
domain.headerComments = make([]string, 0)
|
||||
domain.translations = make(map[string]*Translation)
|
||||
domain.contextTranslations = make(map[string]map[string]*Translation)
|
||||
domain.pluralTranslations = make(map[string]*Translation)
|
||||
|
||||
return domain
|
||||
}
|
||||
|
||||
// SetPluralResolver sets a custom plural resolver function
|
||||
func (do *Domain) SetPluralResolver(f func(int) int) {
|
||||
do.customPluralResolver = f
|
||||
}
|
||||
|
||||
func (do *Domain) pluralForm(n int) int {
|
||||
// do we really need locking here? not sure how this plurals.Expression works, so sticking with it for now
|
||||
do.pluralMutex.RLock()
|
||||
defer do.pluralMutex.RUnlock()
|
||||
|
||||
// Failure fallback
|
||||
if do.pluralforms == nil {
|
||||
if do.customPluralResolver != nil {
|
||||
return do.customPluralResolver(n)
|
||||
}
|
||||
|
||||
/* Use the Germanic plural rule. */
|
||||
if n == 1 {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
return do.pluralforms.Eval(uint32(n))
|
||||
}
|
||||
|
||||
// parseHeaders retrieves data from previously parsed headers. it's called by both Mo and Po when parsing
|
||||
func (do *Domain) parseHeaders() {
|
||||
raw := ""
|
||||
if _, ok := do.translations[raw]; ok {
|
||||
raw = do.translations[raw].Get()
|
||||
}
|
||||
|
||||
// textproto.ReadMIMEHeader() forces keys through CanonicalMIMEHeaderKey(); must read header manually to have one-to-one round-trip of keys
|
||||
languageKey := "Language"
|
||||
pluralFormsKey := "Plural-Forms"
|
||||
|
||||
rawLines := strings.Split(raw, "\n")
|
||||
for _, line := range rawLines {
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
colonIdx := strings.Index(line, ":")
|
||||
if colonIdx < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := line[:colonIdx]
|
||||
lowerKey := strings.ToLower(key)
|
||||
if lowerKey == strings.ToLower(languageKey) {
|
||||
languageKey = key
|
||||
} else if lowerKey == strings.ToLower(pluralFormsKey) {
|
||||
pluralFormsKey = key
|
||||
}
|
||||
|
||||
value := strings.TrimSpace(line[colonIdx+1:])
|
||||
do.Headers.Add(key, value)
|
||||
}
|
||||
|
||||
// Get/save needed headers
|
||||
do.Language = do.Headers.Get(languageKey)
|
||||
do.PluralForms = do.Headers.Get(pluralFormsKey)
|
||||
|
||||
// Parse Plural-Forms formula
|
||||
if do.PluralForms == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Split plural form header value
|
||||
pfs := strings.Split(do.PluralForms, ";")
|
||||
|
||||
// Parse values
|
||||
for _, i := range pfs {
|
||||
vs := strings.SplitN(i, "=", 2)
|
||||
if len(vs) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch strings.TrimSpace(vs[0]) {
|
||||
case "nplurals":
|
||||
do.nplurals, _ = strconv.Atoi(vs[1])
|
||||
|
||||
case "plural":
|
||||
do.plural = vs[1]
|
||||
|
||||
if expr, err := plurals.Compile(do.plural); err == nil {
|
||||
do.pluralforms = expr
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DropStaleTranslations drops any translations stored that have not been Set*()
|
||||
// since 'po' was initialised
|
||||
func (do *Domain) DropStaleTranslations() {
|
||||
do.trMutex.Lock()
|
||||
do.pluralMutex.Lock()
|
||||
defer do.trMutex.Unlock()
|
||||
defer do.pluralMutex.Unlock()
|
||||
|
||||
for name, ctx := range do.contextTranslations {
|
||||
for id, trans := range ctx {
|
||||
if trans.IsStale() {
|
||||
delete(ctx, id)
|
||||
}
|
||||
}
|
||||
if len(ctx) == 0 {
|
||||
delete(do.contextTranslations, name)
|
||||
}
|
||||
}
|
||||
|
||||
for id, trans := range do.translations {
|
||||
if trans.IsStale() {
|
||||
delete(do.translations, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetRefs set source references for a given translation
|
||||
func (do *Domain) SetRefs(str string, refs []string) {
|
||||
do.trMutex.Lock()
|
||||
do.pluralMutex.Lock()
|
||||
defer do.trMutex.Unlock()
|
||||
defer do.pluralMutex.Unlock()
|
||||
|
||||
if trans, ok := do.translations[str]; ok {
|
||||
trans.Refs = refs
|
||||
} else {
|
||||
trans = NewTranslation()
|
||||
trans.ID = str
|
||||
trans.SetRefs(refs)
|
||||
do.translations[str] = trans
|
||||
}
|
||||
}
|
||||
|
||||
// GetRefs get source references for a given translation
|
||||
func (do *Domain) GetRefs(str string) []string {
|
||||
// Sync read
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.translations != nil {
|
||||
if trans, ok := do.translations[str]; ok {
|
||||
return trans.Refs
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set the translation of a given string
|
||||
func (do *Domain) Set(id, str string) {
|
||||
do.trMutex.Lock()
|
||||
do.pluralMutex.Lock()
|
||||
defer do.trMutex.Unlock()
|
||||
defer do.pluralMutex.Unlock()
|
||||
|
||||
if trans, ok := do.translations[id]; ok {
|
||||
trans.Set(str)
|
||||
} else {
|
||||
trans = NewTranslation()
|
||||
trans.ID = id
|
||||
trans.Set(str)
|
||||
do.translations[id] = trans
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves the Translation for the given string.
|
||||
func (do *Domain) Get(str string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.translations != nil {
|
||||
if _, ok := do.translations[str]; ok {
|
||||
return FormatString(do.translations[str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
|
||||
// Append retrieves the Translation for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) Append(b []byte, str string, vars ...interface{}) []byte {
|
||||
// Sync read
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.translations != nil {
|
||||
if _, ok := do.translations[str]; ok {
|
||||
return Appendf(b, do.translations[str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Return the same we received by default
|
||||
return Appendf(b, str, vars...)
|
||||
}
|
||||
|
||||
// SetN sets the (N)th plural form for the given string
|
||||
func (do *Domain) SetN(id, plural string, n int, str string) {
|
||||
// Get plural form _before_ lock down
|
||||
pluralForm := do.pluralForm(n)
|
||||
|
||||
do.trMutex.Lock()
|
||||
do.pluralMutex.Lock()
|
||||
defer do.trMutex.Unlock()
|
||||
defer do.pluralMutex.Unlock()
|
||||
|
||||
if trans, ok := do.translations[id]; ok {
|
||||
trans.SetN(pluralForm, str)
|
||||
} else {
|
||||
trans = NewTranslation()
|
||||
trans.ID = id
|
||||
trans.PluralID = plural
|
||||
trans.SetN(pluralForm, str)
|
||||
do.translations[id] = trans
|
||||
}
|
||||
}
|
||||
|
||||
// GetN retrieves the (N)th plural form of Translation for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
// Sync read
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.translations != nil {
|
||||
if _, ok := do.translations[str]; ok {
|
||||
return FormatString(do.translations[str].GetN(do.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse plural forms to distinguish between plural and singular
|
||||
if do.pluralForm(n) == 0 {
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
return FormatString(plural, vars...)
|
||||
}
|
||||
|
||||
// AppendN adds the (N)th plural form of Translation for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) AppendN(b []byte, str, plural string, n int, vars ...interface{}) []byte {
|
||||
// Sync read
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.translations != nil {
|
||||
if _, ok := do.translations[str]; ok {
|
||||
return Appendf(b, do.translations[str].GetN(do.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse plural forms to distinguish between plural and singular
|
||||
if do.pluralForm(n) == 0 {
|
||||
return Appendf(b, str, vars...)
|
||||
}
|
||||
return Appendf(b, plural, vars...)
|
||||
}
|
||||
|
||||
// SetC sets the translation for the given string in the given context
|
||||
func (do *Domain) SetC(id, ctx, str string) {
|
||||
do.trMutex.Lock()
|
||||
do.pluralMutex.Lock()
|
||||
defer do.trMutex.Unlock()
|
||||
defer do.pluralMutex.Unlock()
|
||||
|
||||
if context, ok := do.contextTranslations[ctx]; ok {
|
||||
if trans, hasTrans := context[id]; hasTrans {
|
||||
trans.Set(str)
|
||||
} else {
|
||||
trans = NewTranslation()
|
||||
trans.ID = id
|
||||
trans.Set(str)
|
||||
context[id] = trans
|
||||
}
|
||||
} else {
|
||||
trans := NewTranslation()
|
||||
trans.ID = id
|
||||
trans.Set(str)
|
||||
do.contextTranslations[ctx] = map[string]*Translation{
|
||||
id: trans,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetC retrieves the corresponding Translation for a given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) GetC(str, ctx string, vars ...interface{}) string {
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.contextTranslations != nil {
|
||||
if _, ok := do.contextTranslations[ctx]; ok {
|
||||
if do.contextTranslations[ctx] != nil {
|
||||
if _, ok := do.contextTranslations[ctx][str]; ok {
|
||||
return FormatString(do.contextTranslations[ctx][str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the string we received by default
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
|
||||
// AppendC retrieves the corresponding Translation for a given string in the given context.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (do *Domain) AppendC(b []byte, str, ctx string, vars ...interface{}) []byte {
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.contextTranslations != nil {
|
||||
if _, ok := do.contextTranslations[ctx]; ok {
|
||||
if do.contextTranslations[ctx] != nil {
|
||||
if _, ok := do.contextTranslations[ctx][str]; ok {
|
||||
return Appendf(b, do.contextTranslations[ctx][str].Get(), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the string we received by default
|
||||
return Appendf(b, str, vars...)
|
||||
}
|
||||
|
||||
// SetNC sets the (N)th plural form for the given string in the given context
|
||||
func (do *Domain) SetNC(id, plural, ctx string, n int, str string) {
|
||||
// Get plural form _before_ lock down
|
||||
pluralForm := do.pluralForm(n)
|
||||
|
||||
do.trMutex.Lock()
|
||||
do.pluralMutex.Lock()
|
||||
defer do.trMutex.Unlock()
|
||||
defer do.pluralMutex.Unlock()
|
||||
|
||||
if context, ok := do.contextTranslations[ctx]; ok {
|
||||
if trans, hasTrans := context[id]; hasTrans {
|
||||
trans.SetN(pluralForm, str)
|
||||
} else {
|
||||
trans = NewTranslation()
|
||||
trans.ID = id
|
||||
trans.SetN(pluralForm, str)
|
||||
context[id] = trans
|
||||
}
|
||||
} else {
|
||||
trans := NewTranslation()
|
||||
trans.ID = id
|
||||
trans.SetN(pluralForm, str)
|
||||
do.contextTranslations[ctx] = map[string]*Translation{
|
||||
id: trans,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetNC retrieves the (N)th plural form of Translation 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 (do *Domain) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.contextTranslations != nil {
|
||||
if _, ok := do.contextTranslations[ctx]; ok {
|
||||
if do.contextTranslations[ctx] != nil {
|
||||
if _, ok := do.contextTranslations[ctx][str]; ok {
|
||||
return FormatString(do.contextTranslations[ctx][str].GetN(do.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
return FormatString(plural, vars...)
|
||||
}
|
||||
|
||||
// AppendNC retrieves the (N)th plural form of Translation 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 (do *Domain) AppendNC(b []byte, str, plural string, n int, ctx string, vars ...interface{}) []byte {
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
if do.contextTranslations != nil {
|
||||
if _, ok := do.contextTranslations[ctx]; ok {
|
||||
if do.contextTranslations[ctx] != nil {
|
||||
if _, ok := do.contextTranslations[ctx][str]; ok {
|
||||
return Appendf(b, do.contextTranslations[ctx][str].GetN(do.pluralForm(n)), vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return Appendf(b, str, vars...)
|
||||
}
|
||||
return Appendf(b, plural, vars...)
|
||||
}
|
||||
|
||||
// IsTranslated reports whether a string is translated
|
||||
func (do *Domain) IsTranslated(str string) bool {
|
||||
return do.IsTranslatedN(str, 1)
|
||||
}
|
||||
|
||||
// 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(do.pluralForm(n))
|
||||
}
|
||||
|
||||
// IsTranslatedC reports whether a context string is translated
|
||||
func (do *Domain) IsTranslatedC(str, ctx string) bool {
|
||||
return do.IsTranslatedNC(str, 1, 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(do.pluralForm(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))
|
||||
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
for msgID, trans := range do.translations {
|
||||
newTrans := NewTranslation()
|
||||
newTrans.ID = trans.ID
|
||||
newTrans.PluralID = trans.PluralID
|
||||
newTrans.dirty = trans.dirty
|
||||
if len(trans.Refs) > 0 {
|
||||
newTrans.Refs = make([]string, len(trans.Refs))
|
||||
copy(newTrans.Refs, trans.Refs)
|
||||
}
|
||||
for k, v := range trans.Trs {
|
||||
newTrans.Trs[k] = v
|
||||
}
|
||||
all[msgID] = newTrans
|
||||
}
|
||||
|
||||
return all
|
||||
}
|
||||
|
||||
// GetCtxTranslations returns a copy of every translation in the domain with context
|
||||
func (do *Domain) GetCtxTranslations() map[string]map[string]*Translation {
|
||||
all := make(map[string]map[string]*Translation, len(do.contextTranslations))
|
||||
|
||||
do.trMutex.RLock()
|
||||
defer do.trMutex.RUnlock()
|
||||
|
||||
for ctx, translations := range do.contextTranslations {
|
||||
for msgID, trans := range translations {
|
||||
newTrans := NewTranslation()
|
||||
newTrans.ID = trans.ID
|
||||
newTrans.PluralID = trans.PluralID
|
||||
newTrans.dirty = trans.dirty
|
||||
if len(trans.Refs) > 0 {
|
||||
newTrans.Refs = make([]string, len(trans.Refs))
|
||||
copy(newTrans.Refs, trans.Refs)
|
||||
}
|
||||
for k, v := range trans.Trs {
|
||||
newTrans.Trs[k] = v
|
||||
}
|
||||
|
||||
if all[ctx] == nil {
|
||||
all[ctx] = make(map[string]*Translation)
|
||||
}
|
||||
|
||||
all[ctx][msgID] = newTrans
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return all
|
||||
}
|
||||
|
||||
// SourceReference is a struct to hold source reference information
|
||||
type SourceReference struct {
|
||||
path string
|
||||
line int
|
||||
context string
|
||||
trans *Translation
|
||||
}
|
||||
|
||||
func extractPathAndLine(ref string) (string, int) {
|
||||
var path string
|
||||
var line int
|
||||
colonIdx := strings.IndexRune(ref, ':')
|
||||
if colonIdx >= 0 {
|
||||
path = ref[:colonIdx]
|
||||
line, _ = strconv.Atoi(ref[colonIdx+1:])
|
||||
} else {
|
||||
path = ref
|
||||
line = 0
|
||||
}
|
||||
return path, line
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler interface
|
||||
// Assists round-trip of POT/PO content
|
||||
func (do *Domain) MarshalText() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
if len(do.headerComments) > 0 {
|
||||
buf.WriteString(strings.Join(do.headerComments, "\n"))
|
||||
buf.WriteByte(byte('\n'))
|
||||
}
|
||||
buf.WriteString("msgid \"\"\nmsgstr \"\"")
|
||||
|
||||
// Standard order consistent with xgettext
|
||||
headerOrder := map[string]int{
|
||||
"project-id-version": 0,
|
||||
"report-msgid-bugs-to": 1,
|
||||
"pot-creation-date": 2,
|
||||
"po-revision-date": 3,
|
||||
"last-translator": 4,
|
||||
"language-team": 5,
|
||||
"language": 6,
|
||||
"mime-version": 7,
|
||||
"content-type": 9,
|
||||
"content-transfer-encoding": 10,
|
||||
"plural-forms": 11,
|
||||
}
|
||||
|
||||
headerKeys := make([]string, 0, len(do.Headers))
|
||||
|
||||
for k := range do.Headers {
|
||||
headerKeys = append(headerKeys, k)
|
||||
}
|
||||
|
||||
sort.Slice(headerKeys, func(i, j int) bool {
|
||||
var iOrder int
|
||||
var jOrder int
|
||||
var ok bool
|
||||
if iOrder, ok = headerOrder[strings.ToLower(headerKeys[i])]; !ok {
|
||||
iOrder = 8
|
||||
}
|
||||
|
||||
if jOrder, ok = headerOrder[strings.ToLower(headerKeys[j])]; !ok {
|
||||
jOrder = 8
|
||||
}
|
||||
|
||||
if iOrder < jOrder {
|
||||
return true
|
||||
}
|
||||
if iOrder > jOrder {
|
||||
return false
|
||||
}
|
||||
return headerKeys[i] < headerKeys[j]
|
||||
})
|
||||
|
||||
for _, k := range headerKeys {
|
||||
// Access Headers map directly so as not to canonicalise
|
||||
v := do.Headers[k]
|
||||
|
||||
for _, value := range v {
|
||||
buf.WriteString("\n\"" + k + ": " + value + "\\n\"")
|
||||
}
|
||||
}
|
||||
|
||||
// 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.contextTranslations {
|
||||
for id, trans := range ctx {
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
if len(trans.Refs) > 0 {
|
||||
path, line := extractPathAndLine(trans.Refs[0])
|
||||
references = append(references, SourceReference{
|
||||
path,
|
||||
line,
|
||||
name,
|
||||
trans,
|
||||
})
|
||||
} else {
|
||||
references = append(references, SourceReference{
|
||||
"",
|
||||
0,
|
||||
name,
|
||||
trans,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for id, trans := range do.translations {
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(trans.Refs) > 0 {
|
||||
path, line := extractPathAndLine(trans.Refs[0])
|
||||
references = append(references, SourceReference{
|
||||
path,
|
||||
line,
|
||||
"",
|
||||
trans,
|
||||
})
|
||||
} else {
|
||||
references = append(references, SourceReference{
|
||||
"",
|
||||
0,
|
||||
"",
|
||||
trans,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(references, func(i, j int) bool {
|
||||
if references[i].path < references[j].path {
|
||||
return true
|
||||
}
|
||||
if references[i].path > references[j].path {
|
||||
return false
|
||||
}
|
||||
if references[i].line < references[j].line {
|
||||
return true
|
||||
}
|
||||
if references[i].line > references[j].line {
|
||||
return false
|
||||
}
|
||||
|
||||
if references[i].context < references[j].context {
|
||||
return true
|
||||
}
|
||||
if references[i].context > references[j].context {
|
||||
return false
|
||||
}
|
||||
return references[i].trans.ID < references[j].trans.ID
|
||||
})
|
||||
|
||||
for _, ref := range references {
|
||||
trans := ref.trans
|
||||
if len(trans.Refs) > 0 {
|
||||
buf.WriteString("\n\n#: " + strings.Join(trans.Refs, " "))
|
||||
} else {
|
||||
buf.WriteByte(byte('\n'))
|
||||
}
|
||||
|
||||
if ref.context == "" {
|
||||
buf.WriteString("\nmsgid \"" + EscapeSpecialCharacters(trans.ID) + "\"")
|
||||
} else {
|
||||
buf.WriteString("\nmsgctxt \"" + EscapeSpecialCharacters(ref.context) + "\"\nmsgid \"" + EscapeSpecialCharacters(trans.ID) + "\"")
|
||||
}
|
||||
|
||||
if trans.PluralID == "" {
|
||||
buf.WriteString("\nmsgstr \"" + EscapeSpecialCharacters(trans.Trs[0]) + "\"")
|
||||
} else {
|
||||
buf.WriteString("\nmsgid_plural \"" + trans.PluralID + "\"")
|
||||
for i, tr := range trans.Trs {
|
||||
buf.WriteString("\nmsgstr[" + EscapeSpecialCharacters(strconv.Itoa(i)) + "] \"" + tr + "\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// EscapeSpecialCharacters escapes special characters in a string
|
||||
func EscapeSpecialCharacters(s string) string {
|
||||
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
|
||||
func (do *Domain) MarshalBinary() ([]byte, error) {
|
||||
obj := new(TranslatorEncoding)
|
||||
obj.Headers = do.Headers
|
||||
obj.Language = do.Language
|
||||
obj.PluralForms = do.PluralForms
|
||||
obj.Nplurals = do.nplurals
|
||||
obj.Plural = do.plural
|
||||
obj.Translations = do.translations
|
||||
obj.Contexts = do.contextTranslations
|
||||
|
||||
var buff bytes.Buffer
|
||||
encoder := gob.NewEncoder(&buff)
|
||||
err := encoder.Encode(obj)
|
||||
|
||||
return buff.Bytes(), err
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding.BinaryUnmarshaler interface
|
||||
func (do *Domain) UnmarshalBinary(data []byte) error {
|
||||
buff := bytes.NewBuffer(data)
|
||||
obj := new(TranslatorEncoding)
|
||||
|
||||
decoder := gob.NewDecoder(buff)
|
||||
err := decoder.Decode(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
do.Headers = obj.Headers
|
||||
do.Language = obj.Language
|
||||
do.PluralForms = obj.PluralForms
|
||||
do.nplurals = obj.Nplurals
|
||||
do.plural = obj.Plural
|
||||
do.translations = obj.Translations
|
||||
do.contextTranslations = obj.Contexts
|
||||
|
||||
if expr, err := plurals.Compile(do.plural); err == nil {
|
||||
do.pluralforms = expr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
427
vendor/github.com/leonelquinteros/gotext/gotext.go
generated
vendored
Normal file
427
vendor/github.com/leonelquinteros/gotext/gotext.go
generated
vendored
Normal file
@ -0,0 +1,427 @@
|
||||
/*
|
||||
Package gotext implements GNU gettext utilities.
|
||||
|
||||
For quick/simple translations you can use the package level functions directly.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configure package
|
||||
gotext.Configure("/path/to/locales/root/dir", "en_UK", "domain-name")
|
||||
|
||||
// Translate text from default domain
|
||||
fmt.Println(gotext.Get("My text on 'domain-name' domain"))
|
||||
|
||||
// Translate text from a different domain without reconfigure
|
||||
fmt.Println(gotext.GetD("domain2", "Another text on a different domain"))
|
||||
}
|
||||
*/
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Global environment variables
|
||||
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.
|
||||
languages []string
|
||||
|
||||
// Storage for package level methods
|
||||
locales []*Locale
|
||||
}
|
||||
|
||||
var globalConfig *config
|
||||
|
||||
// FallbackLocale is the default language to be used when no language is set.
|
||||
var FallbackLocale = "en_US"
|
||||
|
||||
func init() {
|
||||
// Init default configuration
|
||||
globalConfig = &config{
|
||||
domain: "default",
|
||||
languages: []string{FallbackLocale},
|
||||
library: "/usr/local/share/locale",
|
||||
locales: nil,
|
||||
}
|
||||
|
||||
// Register Translator types for gob encoding
|
||||
gob.Register(TranslatorEncoding{})
|
||||
}
|
||||
|
||||
// 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.locales == nil || rebuildCache {
|
||||
var locales []*Locale
|
||||
for _, language := range globalConfig.languages {
|
||||
locales = append(locales, NewLocale(globalConfig.library, language))
|
||||
}
|
||||
globalConfig.locales = locales
|
||||
}
|
||||
|
||||
for _, locale := range globalConfig.locales {
|
||||
if _, ok := locale.Domains[globalConfig.domain]; !ok || rebuildCache {
|
||||
locale.AddDomain(globalConfig.domain)
|
||||
}
|
||||
locale.SetDomain(globalConfig.domain)
|
||||
}
|
||||
|
||||
globalConfig.Unlock()
|
||||
}
|
||||
|
||||
// GetDomain is the domain getter for the package configuration
|
||||
func GetDomain() string {
|
||||
var dom string
|
||||
globalConfig.RLock()
|
||||
if globalConfig.locales != nil {
|
||||
// All locales have the same domain
|
||||
dom = globalConfig.locales[0].GetDomain()
|
||||
}
|
||||
if dom == "" {
|
||||
dom = globalConfig.domain
|
||||
}
|
||||
globalConfig.RUnlock()
|
||||
|
||||
return dom
|
||||
}
|
||||
|
||||
// SetDomain sets the name for the domain to be used at package level.
|
||||
// It reloads the corresponding Translation file.
|
||||
func SetDomain(dom string) {
|
||||
globalConfig.Lock()
|
||||
globalConfig.domain = dom
|
||||
if globalConfig.locales != nil {
|
||||
for _, locale := range globalConfig.locales {
|
||||
locale.SetDomain(dom)
|
||||
}
|
||||
}
|
||||
globalConfig.Unlock()
|
||||
|
||||
loadLocales(true)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
languages := GetLanguages()
|
||||
if len(languages) == 0 {
|
||||
return FallbackLocale
|
||||
}
|
||||
return languages[0]
|
||||
}
|
||||
|
||||
// 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()
|
||||
var languages []string
|
||||
for _, language := range strings.Split(lang, ":") {
|
||||
languages = append(languages, SimplifiedLocale(language))
|
||||
}
|
||||
globalConfig.languages = languages
|
||||
globalConfig.Unlock()
|
||||
|
||||
loadLocales(true)
|
||||
}
|
||||
|
||||
// GetLibrary is the library getter for the package configuration
|
||||
func GetLibrary() string {
|
||||
globalConfig.RLock()
|
||||
lib := globalConfig.library
|
||||
globalConfig.RUnlock()
|
||||
|
||||
return lib
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
loadLocales(true)
|
||||
}
|
||||
|
||||
// GetLocales returns the locales that have been set for the package configuration.
|
||||
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.
|
||||
// It receives the library path, language code and domain name.
|
||||
// This function is recommended to be used when changing more than one setting,
|
||||
// as using each setter will introduce a I/O overhead because the Translation file will be loaded after each set.
|
||||
func Configure(lib, lang, dom string) {
|
||||
globalConfig.Lock()
|
||||
globalConfig.library = lib
|
||||
var languages []string
|
||||
for _, language := range strings.Split(lang, ":") {
|
||||
languages = append(languages, SimplifiedLocale(language))
|
||||
}
|
||||
globalConfig.languages = languages
|
||||
globalConfig.domain = dom
|
||||
globalConfig.Unlock()
|
||||
|
||||
loadLocales(true)
|
||||
}
|
||||
|
||||
// Get uses the default domain globally set 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 Get(str string, vars ...interface{}) string {
|
||||
return GetD(GetDomain(), str, vars...)
|
||||
}
|
||||
|
||||
// GetN retrieves the (N)th plural form of Translation for the given string in the default domain.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return GetND(GetDomain(), str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// 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 Locales
|
||||
loadLocales(false)
|
||||
|
||||
globalConfig.RLock()
|
||||
defer globalConfig.RUnlock()
|
||||
|
||||
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
|
||||
}
|
||||
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 Locales
|
||||
loadLocales(false)
|
||||
|
||||
globalConfig.RLock()
|
||||
defer globalConfig.RUnlock()
|
||||
|
||||
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
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
// GetC uses the default domain globally set to return the corresponding Translation of 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 GetC(str, ctx string, vars ...interface{}) string {
|
||||
return GetDC(GetDomain(), str, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetNC retrieves the (N)th plural form of Translation for the given string in the given context in the default domain.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return GetNDC(GetDomain(), str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// 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 Locales
|
||||
loadLocales(false)
|
||||
|
||||
globalConfig.RLock()
|
||||
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 Locales
|
||||
loadLocales(false)
|
||||
|
||||
// Return Translation
|
||||
globalConfig.RLock()
|
||||
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, 1, 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, 1, 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, 1, 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
|
||||
}
|
96
vendor/github.com/leonelquinteros/gotext/helper.go
generated
vendored
Normal file
96
vendor/github.com/leonelquinteros/gotext/helper.go
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var re = regexp.MustCompile(`%\(([a-zA-Z0-9_]+)\)[.0-9]*[svTtbcdoqXxUeEfFgGp]`)
|
||||
|
||||
// SimplifiedLocale simplified locale like " en_US"/"de_DE "/en_US.UTF-8/zh_CN/zh_TW/el_GR@euro/... to en_US, de_DE, zh_CN, el_GR...
|
||||
func SimplifiedLocale(lang string) string {
|
||||
// en_US/en_US.UTF-8/zh_CN/zh_TW/el_GR@euro/...
|
||||
if idx := strings.Index(lang, ":"); idx != -1 {
|
||||
lang = lang[:idx]
|
||||
}
|
||||
if idx := strings.Index(lang, "@"); idx != -1 {
|
||||
lang = lang[:idx]
|
||||
}
|
||||
if idx := strings.Index(lang, "."); idx != -1 {
|
||||
lang = lang[:idx]
|
||||
}
|
||||
return strings.TrimSpace(lang)
|
||||
}
|
||||
|
||||
// FormatString applies text formatting only when needed to parse variables.
|
||||
func FormatString(str string, vars ...interface{}) string {
|
||||
if len(vars) > 0 {
|
||||
return fmt.Sprintf(str, vars...)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// Appendf applies text formatting only when needed to parse variables.
|
||||
func Appendf(b []byte, str string, vars ...interface{}) []byte {
|
||||
if len(vars) > 0 {
|
||||
return fmt.Appendf(b, str, vars...)
|
||||
}
|
||||
|
||||
return append(b, str...)
|
||||
}
|
||||
|
||||
// NPrintf support named format
|
||||
// NPrintf("%(name)s is Type %(type)s", map[string]interface{}{"name": "Gotext", "type": "struct"})
|
||||
func NPrintf(format string, params map[string]interface{}) {
|
||||
f, p := parseSprintf(format, params)
|
||||
fmt.Printf(f, p...)
|
||||
}
|
||||
|
||||
// Sprintf support named format
|
||||
//
|
||||
// Sprintf("%(name)s is Type %(type)s", map[string]interface{}{"name": "Gotext", "type": "struct"})
|
||||
func Sprintf(format string, params map[string]interface{}) string {
|
||||
f, p := parseSprintf(format, params)
|
||||
return fmt.Sprintf(f, p...)
|
||||
}
|
||||
|
||||
func parseSprintf(format string, params map[string]interface{}) (string, []interface{}) {
|
||||
f, n := reformatSprintf(format)
|
||||
var p []interface{}
|
||||
for _, v := range n {
|
||||
p = append(p, params[v])
|
||||
}
|
||||
return f, p
|
||||
}
|
||||
|
||||
func reformatSprintf(f string) (string, []string) {
|
||||
m := re.FindAllStringSubmatch(f, -1)
|
||||
i := re.FindAllStringSubmatchIndex(f, -1)
|
||||
|
||||
ord := []string{}
|
||||
for _, v := range m {
|
||||
ord = append(ord, v[1])
|
||||
}
|
||||
|
||||
pair := []int{0}
|
||||
for _, v := range i {
|
||||
pair = append(pair, v[2]-1)
|
||||
pair = append(pair, v[3]+1)
|
||||
}
|
||||
pair = append(pair, len(f))
|
||||
plen := len(pair)
|
||||
|
||||
out := ""
|
||||
for n := 0; n < plen; n += 2 {
|
||||
out += f[pair[n]:pair[n+1]]
|
||||
}
|
||||
|
||||
return out, ord
|
||||
}
|
25
vendor/github.com/leonelquinteros/gotext/introspector.go
generated
vendored
Normal file
25
vendor/github.com/leonelquinteros/gotext/introspector.go
generated
vendored
Normal 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
|
||||
}
|
478
vendor/github.com/leonelquinteros/gotext/locale.go
generated
vendored
Normal file
478
vendor/github.com/leonelquinteros/gotext/locale.go
generated
vendored
Normal file
@ -0,0 +1,478 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
)
|
||||
|
||||
/*
|
||||
Locale wraps the entire i18n collection for a single language (locale)
|
||||
It's used by the package functions, but it can also be used independently to handle
|
||||
multiple languages at the same time by working with this object.
|
||||
|
||||
Example:
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create Locale with library path and language code
|
||||
l := gotext.NewLocale("/path/to/i18n/dir", "en_US")
|
||||
|
||||
// Load domain '/path/to/i18n/dir/en_US/LC_MESSAGES/default.{po,mo}'
|
||||
l.AddDomain("default")
|
||||
|
||||
// Translate text from default domain
|
||||
fmt.Println(l.Get("Translate this"))
|
||||
|
||||
// Load different domain ('/path/to/i18n/dir/en_US/LC_MESSAGES/extras.{po,mo}')
|
||||
l.AddDomain("extras")
|
||||
|
||||
// Translate text from domain
|
||||
fmt.Println(l.GetD("extras", "Translate this"))
|
||||
}
|
||||
*/
|
||||
type Locale struct {
|
||||
// Path to locale files.
|
||||
path string
|
||||
|
||||
// Language for this Locale
|
||||
lang string
|
||||
|
||||
// List of available Domains for this locale.
|
||||
Domains map[string]Translator
|
||||
|
||||
// First AddDomain is default Domain
|
||||
defaultDomain string
|
||||
|
||||
// Sync Mutex
|
||||
sync.RWMutex
|
||||
|
||||
// optional fs to use
|
||||
fs fs.FS
|
||||
}
|
||||
|
||||
// NewLocale creates and initializes a new Locale object for a given language.
|
||||
// It receives a path for the i18n .po/.mo files directory (p) and a language code to use (l).
|
||||
func NewLocale(p, l string) *Locale {
|
||||
return &Locale{
|
||||
path: p,
|
||||
lang: SimplifiedLocale(l),
|
||||
Domains: make(map[string]Translator),
|
||||
}
|
||||
}
|
||||
|
||||
// NewLocaleFS returns a Locale working with a fs.FS
|
||||
func NewLocaleFS(l string, filesystem fs.FS) *Locale {
|
||||
loc := NewLocale("", l)
|
||||
loc.fs = filesystem
|
||||
return loc
|
||||
}
|
||||
|
||||
// NewLocaleFSWithPath returns a Locale working with a fs.FS on a p path folder.
|
||||
func NewLocaleFSWithPath(l string, filesystem fs.FS, p string) *Locale {
|
||||
loc := NewLocale("", l)
|
||||
loc.fs = filesystem
|
||||
loc.path = p
|
||||
return loc
|
||||
}
|
||||
|
||||
func (l *Locale) findExt(dom, ext string) string {
|
||||
filename := path.Join(l.path, l.lang, "LC_MESSAGES", dom+"."+ext)
|
||||
if l.fileExists(filename) {
|
||||
return filename
|
||||
}
|
||||
|
||||
if len(l.lang) > 2 {
|
||||
filename = path.Join(l.path, l.lang[:2], "LC_MESSAGES", dom+"."+ext)
|
||||
if l.fileExists(filename) {
|
||||
return filename
|
||||
}
|
||||
}
|
||||
|
||||
filename = path.Join(l.path, l.lang, dom+"."+ext)
|
||||
if l.fileExists(filename) {
|
||||
return filename
|
||||
}
|
||||
|
||||
if len(l.lang) > 2 {
|
||||
filename = path.Join(l.path, l.lang[:2], dom+"."+ext)
|
||||
if l.fileExists(filename) {
|
||||
return filename
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
_, err := fs.Stat(l.fs, filename)
|
||||
return err == nil
|
||||
}
|
||||
_, err := os.Stat(filename)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// AddDomain creates a new domain for a given locale object and initializes the Po object.
|
||||
// If the domain exists, it gets reloaded.
|
||||
func (l *Locale) AddDomain(dom string) {
|
||||
var poObj Translator
|
||||
|
||||
file := l.findExt(dom, "po")
|
||||
if file != "" {
|
||||
poObj = NewPoFS(l.fs)
|
||||
// Parse file.
|
||||
poObj.ParseFile(file)
|
||||
} else {
|
||||
file = l.findExt(dom, "mo")
|
||||
if file != "" {
|
||||
poObj = NewMoFS(l.fs)
|
||||
// Parse file.
|
||||
poObj.ParseFile(file)
|
||||
} else {
|
||||
// fallback return if no file found with
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Save new domain
|
||||
l.Lock()
|
||||
|
||||
if l.Domains == nil {
|
||||
l.Domains = make(map[string]Translator)
|
||||
}
|
||||
if l.defaultDomain == "" {
|
||||
l.defaultDomain = dom
|
||||
}
|
||||
l.Domains[dom] = poObj
|
||||
|
||||
// Unlock "Save new domain"
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
// AddTranslator takes a domain name and a Translator object to make it available in the Locale object.
|
||||
func (l *Locale) AddTranslator(dom string, tr Translator) {
|
||||
l.Lock()
|
||||
|
||||
if l.Domains == nil {
|
||||
l.Domains = make(map[string]Translator)
|
||||
}
|
||||
if l.defaultDomain == "" {
|
||||
l.defaultDomain = dom
|
||||
}
|
||||
l.Domains[dom] = tr
|
||||
|
||||
l.Unlock()
|
||||
}
|
||||
|
||||
// GetDomain is the domain getter for Locale configuration
|
||||
func (l *Locale) GetDomain() string {
|
||||
l.RLock()
|
||||
dom := l.defaultDomain
|
||||
l.RUnlock()
|
||||
return dom
|
||||
}
|
||||
|
||||
// SetDomain sets the name for the domain to be used.
|
||||
func (l *Locale) SetDomain(dom string) {
|
||||
l.Lock()
|
||||
l.defaultDomain = dom
|
||||
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 {
|
||||
return l.GetD(l.GetDomain(), str, vars...)
|
||||
}
|
||||
|
||||
// GetN retrieves the (N)th plural form of Translation for the given string in the "default" domain.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return l.GetND(l.GetDomain(), str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// GetD returns the corresponding Translation in the given domain for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetD(dom, str string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].Get(str, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
|
||||
// GetND retrieves the (N)th plural form of Translation in the given domain for the given string.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetND(dom, str, plural string, n int, vars ...interface{}) string {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetN(str, plural, n, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use western default rule (plural > 1) to handle missing domain default result.
|
||||
if n == 1 {
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
return FormatString(plural, vars...)
|
||||
}
|
||||
|
||||
// GetC uses a domain "default" to return the corresponding Translation of 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 (l *Locale) GetC(str, ctx string, vars ...interface{}) string {
|
||||
return l.GetDC(l.GetDomain(), str, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetNC retrieves the (N)th plural form of Translation for the given string in the given context in the "default" domain.
|
||||
// Supports optional parameters (vars... interface{}) to be inserted on the formatted string using the fmt.Printf syntax.
|
||||
func (l *Locale) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return l.GetNDC(l.GetDomain(), str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// 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 (l *Locale) GetDC(dom, str, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetC(str, ctx, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
|
||||
// GetNDC retrieves the (N)th plural form of 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 (l *Locale) GetNDC(dom, str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
// Sync read
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
|
||||
if l.Domains != nil {
|
||||
if _, ok := l.Domains[dom]; ok {
|
||||
if l.Domains[dom] != nil {
|
||||
return l.Domains[dom].GetNC(str, plural, n, ctx, vars...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use western default rule (plural > 1) to handle missing domain default result.
|
||||
if n == 1 {
|
||||
return FormatString(str, vars...)
|
||||
}
|
||||
return FormatString(plural, vars...)
|
||||
}
|
||||
|
||||
// GetTranslations returns a copy of all translations in all domains of this locale. It does not support contexts.
|
||||
func (l *Locale) GetTranslations() map[string]*Translation {
|
||||
all := make(map[string]*Translation)
|
||||
|
||||
l.RLock()
|
||||
defer l.RUnlock()
|
||||
for _, translator := range l.Domains {
|
||||
for msgID, trans := range translator.GetDomain().GetTranslations() {
|
||||
all[msgID] = trans
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
Lang string
|
||||
Domains map[string][]byte
|
||||
DefaultDomain string
|
||||
}
|
||||
|
||||
// MarshalBinary implements encoding BinaryMarshaler interface
|
||||
func (l *Locale) MarshalBinary() ([]byte, error) {
|
||||
obj := new(LocaleEncoding)
|
||||
obj.DefaultDomain = l.defaultDomain
|
||||
obj.Domains = make(map[string][]byte)
|
||||
for k, v := range l.Domains {
|
||||
var err error
|
||||
obj.Domains[k], err = v.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
obj.Lang = l.lang
|
||||
obj.Path = l.path
|
||||
|
||||
var buff bytes.Buffer
|
||||
encoder := gob.NewEncoder(&buff)
|
||||
err := encoder.Encode(obj)
|
||||
|
||||
return buff.Bytes(), err
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements encoding BinaryUnmarshaler interface
|
||||
func (l *Locale) UnmarshalBinary(data []byte) error {
|
||||
buff := bytes.NewBuffer(data)
|
||||
obj := new(LocaleEncoding)
|
||||
|
||||
decoder := gob.NewDecoder(buff)
|
||||
err := decoder.Decode(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.defaultDomain = obj.DefaultDomain
|
||||
l.lang = obj.Lang
|
||||
l.path = obj.Path
|
||||
|
||||
// Decode Domains
|
||||
l.Domains = make(map[string]Translator)
|
||||
for k, v := range obj.Domains {
|
||||
var tr TranslatorEncoding
|
||||
buff := bytes.NewBuffer(v)
|
||||
trDecoder := gob.NewDecoder(buff)
|
||||
err := trDecoder.Decode(&tr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.Domains[k] = tr.GetTranslator()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
319
vendor/github.com/leonelquinteros/gotext/mo.go
generated
vendored
Normal file
319
vendor/github.com/leonelquinteros/gotext/mo.go
generated
vendored
Normal file
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
const (
|
||||
// MoMagicLittleEndian encoding
|
||||
MoMagicLittleEndian = 0x950412de
|
||||
// MoMagicBigEndian encoding
|
||||
MoMagicBigEndian = 0xde120495
|
||||
|
||||
// EotSeparator msgctxt and msgid separator
|
||||
EotSeparator = "\x04"
|
||||
// NulSeparator msgid and msgstr separator
|
||||
NulSeparator = "\x00"
|
||||
)
|
||||
|
||||
/*
|
||||
Mo parses the content of any MO file and provides all the Translation functions needed.
|
||||
It's the base object used by all package methods.
|
||||
And it's safe for concurrent use by multiple goroutines by using the sync package for locking.
|
||||
|
||||
Example:
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create mo object
|
||||
mo := gotext.NewMo()
|
||||
|
||||
// Parse .mo file
|
||||
mo.ParseFile("/path/to/po/file/translations.mo")
|
||||
|
||||
// Get Translation
|
||||
fmt.Println(mo.Get("Translate this"))
|
||||
}
|
||||
*/
|
||||
type Mo struct {
|
||||
// these three public members are for backwards compatibility. they are just set to the value in the domain
|
||||
Headers HeaderMap
|
||||
Language string
|
||||
PluralForms string
|
||||
domain *Domain
|
||||
fs fs.FS
|
||||
}
|
||||
|
||||
// NewMo should always be used to instantiate a new Mo object
|
||||
func NewMo() *Mo {
|
||||
mo := new(Mo)
|
||||
mo.domain = NewDomain()
|
||||
|
||||
return mo
|
||||
}
|
||||
|
||||
// NewMoFS works like NewMO but adds an optional fs.FS
|
||||
func NewMoFS(filesystem fs.FS) *Mo {
|
||||
mo := NewMo()
|
||||
mo.fs = filesystem
|
||||
return mo
|
||||
}
|
||||
|
||||
// GetDomain returns the domain object
|
||||
func (mo *Mo) GetDomain() *Domain {
|
||||
return mo.domain
|
||||
}
|
||||
|
||||
// Get returns the translation for the given string
|
||||
func (mo *Mo) Get(str string, vars ...interface{}) string {
|
||||
return mo.domain.Get(str, vars...)
|
||||
}
|
||||
|
||||
// Append a translation string into the domain
|
||||
func (mo *Mo) Append(b []byte, str string, vars ...interface{}) []byte {
|
||||
return mo.domain.Append(b, str, vars...)
|
||||
}
|
||||
|
||||
// GetN returns the translation for the given string and plural form
|
||||
func (mo *Mo) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return mo.domain.GetN(str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// AppendN appends a translation string for the given plural form into the domain
|
||||
func (mo *Mo) AppendN(b []byte, str, plural string, n int, vars ...interface{}) []byte {
|
||||
return mo.domain.AppendN(b, str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// GetC returns the translation for the given string and context
|
||||
func (mo *Mo) GetC(str, ctx string, vars ...interface{}) string {
|
||||
return mo.domain.GetC(str, ctx, vars...)
|
||||
}
|
||||
|
||||
// AppendC appends a translation string for the given context into the domain
|
||||
func (mo *Mo) AppendC(b []byte, str, ctx string, vars ...interface{}) []byte {
|
||||
return mo.domain.AppendC(b, str, ctx, vars...)
|
||||
}
|
||||
|
||||
// GetNC returns the translation for the given string, plural form and context
|
||||
func (mo *Mo) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return mo.domain.GetNC(str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// AppendNC appends a translation string for the given plural form and context into the domain
|
||||
func (mo *Mo) AppendNC(b []byte, str, plural string, n int, ctx string, vars ...interface{}) []byte {
|
||||
return mo.domain.AppendNC(b, str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// IsTranslated checks if the given string is translated
|
||||
func (mo *Mo) IsTranslated(str string) bool {
|
||||
return mo.domain.IsTranslated(str)
|
||||
}
|
||||
|
||||
// IsTranslatedN checks if the given string is translated with plural form
|
||||
func (mo *Mo) IsTranslatedN(str string, n int) bool {
|
||||
return mo.domain.IsTranslatedN(str, n)
|
||||
}
|
||||
|
||||
// IsTranslatedC checks if the given string is translated with context
|
||||
func (mo *Mo) IsTranslatedC(str, ctx string) bool {
|
||||
return mo.domain.IsTranslatedC(str, ctx)
|
||||
}
|
||||
|
||||
// IsTranslatedNC checks if the given string is translated with plural form and context
|
||||
func (mo *Mo) IsTranslatedNC(str string, n int, ctx string) bool {
|
||||
return mo.domain.IsTranslatedNC(str, n, ctx)
|
||||
}
|
||||
|
||||
// MarshalBinary marshals the Mo object into a binary format
|
||||
func (mo *Mo) MarshalBinary() ([]byte, error) {
|
||||
return mo.domain.MarshalBinary()
|
||||
}
|
||||
|
||||
// UnmarshalBinary unmarshals the Mo object from a binary format
|
||||
func (mo *Mo) UnmarshalBinary(data []byte) error {
|
||||
return mo.domain.UnmarshalBinary(data)
|
||||
}
|
||||
|
||||
// ParseFile loads the translations specified in the provided file, in the GNU gettext .mo format
|
||||
func (mo *Mo) ParseFile(f string) {
|
||||
data, err := getFileData(f, mo.fs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mo.Parse(data)
|
||||
}
|
||||
|
||||
// Parse loads the translations specified in the provided byte slice, in the GNU gettext .mo format
|
||||
func (mo *Mo) Parse(buf []byte) {
|
||||
// Lock while parsing
|
||||
mo.domain.trMutex.Lock()
|
||||
mo.domain.pluralMutex.Lock()
|
||||
defer mo.domain.trMutex.Unlock()
|
||||
defer mo.domain.pluralMutex.Unlock()
|
||||
|
||||
r := bytes.NewReader(buf)
|
||||
|
||||
var magicNumber uint32
|
||||
if err := binary.Read(r, binary.LittleEndian, &magicNumber); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
var bo binary.ByteOrder
|
||||
switch magicNumber {
|
||||
case MoMagicLittleEndian:
|
||||
bo = binary.LittleEndian
|
||||
case MoMagicBigEndian:
|
||||
bo = binary.BigEndian
|
||||
default:
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", "invalid magic number")
|
||||
}
|
||||
|
||||
var header struct {
|
||||
MajorVersion uint16
|
||||
MinorVersion uint16
|
||||
MsgIDCount uint32
|
||||
MsgIDOffset uint32
|
||||
MsgStrOffset uint32
|
||||
HashSize uint32
|
||||
HashOffset uint32
|
||||
}
|
||||
if err := binary.Read(r, bo, &header); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
if v := header.MajorVersion; v != 0 && v != 1 {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", "invalid version number")
|
||||
}
|
||||
if v := header.MinorVersion; v != 0 && v != 1 {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", "invalid version number")
|
||||
}
|
||||
|
||||
msgIDStart := make([]uint32, header.MsgIDCount)
|
||||
msgIDLen := make([]uint32, header.MsgIDCount)
|
||||
if _, err := r.Seek(int64(header.MsgIDOffset), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
for i := 0; i < int(header.MsgIDCount); i++ {
|
||||
if err := binary.Read(r, bo, &msgIDLen[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
if err := binary.Read(r, bo, &msgIDStart[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
msgStrStart := make([]int32, header.MsgIDCount)
|
||||
msgStrLen := make([]int32, header.MsgIDCount)
|
||||
if _, err := r.Seek(int64(header.MsgStrOffset), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
for i := 0; i < int(header.MsgIDCount); i++ {
|
||||
if err := binary.Read(r, bo, &msgStrLen[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
if err := binary.Read(r, bo, &msgStrStart[i]); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < int(header.MsgIDCount); i++ {
|
||||
if _, err := r.Seek(int64(msgIDStart[i]), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
msgIDData := make([]byte, msgIDLen[i])
|
||||
if _, err := r.Read(msgIDData); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
|
||||
if _, err := r.Seek(int64(msgStrStart[i]), 0); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
msgStrData := make([]byte, msgStrLen[i])
|
||||
if _, err := r.Read(msgStrData); err != nil {
|
||||
return
|
||||
// return fmt.Errorf("gettext: %v", err)
|
||||
}
|
||||
|
||||
if len(msgIDData) == 0 {
|
||||
mo.addTranslation(msgIDData, msgStrData)
|
||||
} else {
|
||||
mo.addTranslation(msgIDData, msgStrData)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse headers
|
||||
mo.domain.parseHeaders()
|
||||
|
||||
// set values on this struct
|
||||
// this is for backwards compatibility
|
||||
mo.Language = mo.domain.Language
|
||||
mo.PluralForms = mo.domain.PluralForms
|
||||
mo.Headers = mo.domain.Headers
|
||||
}
|
||||
|
||||
func (mo *Mo) addTranslation(msgid, msgstr []byte) {
|
||||
translation := NewTranslation()
|
||||
var msgctxt []byte
|
||||
var msgidPlural []byte
|
||||
|
||||
d := bytes.Split(msgid, []byte(EotSeparator))
|
||||
if len(d) == 1 {
|
||||
msgid = d[0]
|
||||
} else {
|
||||
msgid, msgctxt = d[1], d[0]
|
||||
}
|
||||
|
||||
dd := bytes.Split(msgid, []byte(NulSeparator))
|
||||
if len(dd) > 1 {
|
||||
msgid = dd[0]
|
||||
dd = dd[1:]
|
||||
}
|
||||
|
||||
translation.ID = string(msgid)
|
||||
|
||||
msgidPlural = bytes.Join(dd, []byte(NulSeparator))
|
||||
if len(msgidPlural) > 0 {
|
||||
translation.PluralID = string(msgidPlural)
|
||||
}
|
||||
|
||||
ddd := bytes.Split(msgstr, []byte(NulSeparator))
|
||||
if len(ddd) > 0 {
|
||||
for i, s := range ddd {
|
||||
translation.Trs[i] = string(s)
|
||||
}
|
||||
}
|
||||
|
||||
if len(msgctxt) > 0 {
|
||||
// With context...
|
||||
if _, ok := mo.domain.contextTranslations[string(msgctxt)]; !ok {
|
||||
mo.domain.contextTranslations[string(msgctxt)] = make(map[string]*Translation)
|
||||
}
|
||||
mo.domain.contextTranslations[string(msgctxt)][translation.ID] = translation
|
||||
} else {
|
||||
mo.domain.translations[translation.ID] = translation
|
||||
}
|
||||
}
|
433
vendor/github.com/leonelquinteros/gotext/plurals/compiler.go
generated
vendored
Normal file
433
vendor/github.com/leonelquinteros/gotext/plurals/compiler.go
generated
vendored
Normal file
@ -0,0 +1,433 @@
|
||||
// Original work Copyright (c) 2016 Jonas Obrist (https://github.com/ojii/gettext.go)
|
||||
// Modified work Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com
|
||||
// Modified work Copyright (c) 2018-present gotext maintainers (https://github.com/leonelquinteros/gotext)
|
||||
//
|
||||
// Licensed under the 3-Clause BSD License. See LICENSE in the project root for license information.
|
||||
|
||||
/*
|
||||
Package plurals is the pluralform compiler to get the correct translation id of the plural string
|
||||
*/
|
||||
package plurals
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type match struct {
|
||||
openPos int
|
||||
closePos int
|
||||
}
|
||||
|
||||
var pat = regexp.MustCompile(`(\?|:|\|\||&&|==|!=|>=|>|<=|<|%|\d+|n)`)
|
||||
|
||||
type testToken interface {
|
||||
compile(tokens []string) (test test, err error)
|
||||
}
|
||||
|
||||
type cmpTestBuilder func(val uint32, flipped bool) test
|
||||
type logicTestBuild func(left test, right test) test
|
||||
|
||||
var ternaryToken ternaryStruct
|
||||
|
||||
type ternaryStruct struct{}
|
||||
|
||||
func (ternaryStruct) compile(tokens []string) (expr Expression, err error) {
|
||||
main, err := splitTokens(tokens, "?")
|
||||
if err != nil {
|
||||
return expr, err
|
||||
}
|
||||
test, err := compileTest(strings.Join(main.Left, ""))
|
||||
if err != nil {
|
||||
return expr, err
|
||||
}
|
||||
actions, err := splitTokens(main.Right, ":")
|
||||
if err != nil {
|
||||
return expr, err
|
||||
}
|
||||
trueAction, err := compileExpression(strings.Join(actions.Left, ""))
|
||||
if err != nil {
|
||||
return expr, err
|
||||
}
|
||||
falseAction, err := compileExpression(strings.Join(actions.Right, ""))
|
||||
if err != nil {
|
||||
return expr, nil
|
||||
}
|
||||
return ternary{
|
||||
test: test,
|
||||
trueExpr: trueAction,
|
||||
falseExpr: falseAction,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var constToken constValStruct
|
||||
|
||||
type constValStruct struct{}
|
||||
|
||||
func (constValStruct) compile(tokens []string) (expr Expression, err error) {
|
||||
if len(tokens) == 0 {
|
||||
return expr, errors.New("got nothing instead of constant")
|
||||
}
|
||||
if len(tokens) != 1 {
|
||||
return expr, fmt.Errorf("invalid constant: %s", strings.Join(tokens, ""))
|
||||
}
|
||||
i, err := strconv.Atoi(tokens[0])
|
||||
if err != nil {
|
||||
return expr, err
|
||||
}
|
||||
return constValue{value: i}, nil
|
||||
}
|
||||
|
||||
func compileLogicTest(tokens []string, sep string, builder logicTestBuild) (test test, err error) {
|
||||
split, err := splitTokens(tokens, sep)
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
left, err := compileTest(strings.Join(split.Left, ""))
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
right, err := compileTest(strings.Join(split.Right, ""))
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
return builder(left, right), nil
|
||||
}
|
||||
|
||||
var orToken orStruct
|
||||
|
||||
type orStruct struct{}
|
||||
|
||||
func (orStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileLogicTest(tokens, "||", buildOr)
|
||||
}
|
||||
func buildOr(left test, right test) test {
|
||||
return or{left: left, right: right}
|
||||
}
|
||||
|
||||
var andToken andStruct
|
||||
|
||||
type andStruct struct{}
|
||||
|
||||
func (andStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileLogicTest(tokens, "&&", buildAnd)
|
||||
}
|
||||
func buildAnd(left test, right test) test {
|
||||
return and{left: left, right: right}
|
||||
}
|
||||
|
||||
func compileMod(tokens []string) (math math, err error) {
|
||||
split, err := splitTokens(tokens, "%")
|
||||
if err != nil {
|
||||
return math, err
|
||||
}
|
||||
if len(split.Left) != 1 || split.Left[0] != "n" {
|
||||
return math, errors.New("modulus operation requires 'n' as left operand")
|
||||
}
|
||||
if len(split.Right) != 1 {
|
||||
return math, errors.New("modulus operation requires simple integer as right operand")
|
||||
}
|
||||
i, err := parseUint32(split.Right[0])
|
||||
if err != nil {
|
||||
return math, err
|
||||
}
|
||||
return mod{value: uint32(i)}, nil
|
||||
}
|
||||
|
||||
func subPipe(modTokens []string, actionTokens []string, builder cmpTestBuilder, flipped bool) (test test, err error) {
|
||||
modifier, err := compileMod(modTokens)
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
if len(actionTokens) != 1 {
|
||||
return test, errors.New("can only get modulus of integer")
|
||||
}
|
||||
i, err := parseUint32(actionTokens[0])
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
action := builder(uint32(i), flipped)
|
||||
return pipe{
|
||||
modifier: modifier,
|
||||
action: action,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func compileEquality(tokens []string, sep string, builder cmpTestBuilder) (test test, err error) {
|
||||
split, err := splitTokens(tokens, sep)
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
if len(split.Left) == 1 && split.Left[0] == "n" {
|
||||
if len(split.Right) != 1 {
|
||||
return test, errors.New("test can only compare n to integers")
|
||||
}
|
||||
i, err := parseUint32(split.Right[0])
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
return builder(i, false), nil
|
||||
} else if len(split.Right) == 1 && split.Right[0] == "n" {
|
||||
if len(split.Left) != 1 {
|
||||
return test, errors.New("test can only compare n to integers")
|
||||
}
|
||||
i, err := parseUint32(split.Left[0])
|
||||
if err != nil {
|
||||
return test, err
|
||||
}
|
||||
return builder(i, true), nil
|
||||
} else if contains(split.Left, "n") && contains(split.Left, "%") {
|
||||
return subPipe(split.Left, split.Right, builder, false)
|
||||
}
|
||||
return test, errors.New("equality test must have 'n' as one of the two tests")
|
||||
|
||||
}
|
||||
|
||||
var eqToken eqStruct
|
||||
|
||||
type eqStruct struct{}
|
||||
|
||||
func (eqStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileEquality(tokens, "==", buildEq)
|
||||
}
|
||||
func buildEq(val uint32, flipped bool) test {
|
||||
return equal{value: val}
|
||||
}
|
||||
|
||||
var neqToken neqStruct
|
||||
|
||||
type neqStruct struct{}
|
||||
|
||||
func (neqStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileEquality(tokens, "!=", buildNeq)
|
||||
}
|
||||
func buildNeq(val uint32, flipped bool) test {
|
||||
return notequal{value: val}
|
||||
}
|
||||
|
||||
var gtToken gtStruct
|
||||
|
||||
type gtStruct struct{}
|
||||
|
||||
func (gtStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileEquality(tokens, ">", buildGt)
|
||||
}
|
||||
func buildGt(val uint32, flipped bool) test {
|
||||
return gt{value: val, flipped: flipped}
|
||||
}
|
||||
|
||||
var gteToken gteStruct
|
||||
|
||||
type gteStruct struct{}
|
||||
|
||||
func (gteStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileEquality(tokens, ">=", buildGte)
|
||||
}
|
||||
func buildGte(val uint32, flipped bool) test {
|
||||
return gte{value: val, flipped: flipped}
|
||||
}
|
||||
|
||||
var ltToken ltStruct
|
||||
|
||||
type ltStruct struct{}
|
||||
|
||||
func (ltStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileEquality(tokens, "<", buildLt)
|
||||
}
|
||||
func buildLt(val uint32, flipped bool) test {
|
||||
return lt{value: val, flipped: flipped}
|
||||
}
|
||||
|
||||
var lteToken lteStruct
|
||||
|
||||
type lteStruct struct{}
|
||||
|
||||
func (lteStruct) compile(tokens []string) (test test, err error) {
|
||||
return compileEquality(tokens, "<=", buildLte)
|
||||
}
|
||||
func buildLte(val uint32, flipped bool) test {
|
||||
return lte{value: val, flipped: flipped}
|
||||
}
|
||||
|
||||
type testTokenDef struct {
|
||||
op string
|
||||
token testToken
|
||||
}
|
||||
|
||||
var precedence = []testTokenDef{
|
||||
{op: "||", token: orToken},
|
||||
{op: "&&", token: andToken},
|
||||
{op: "==", token: eqToken},
|
||||
{op: "!=", token: neqToken},
|
||||
{op: ">=", token: gteToken},
|
||||
{op: ">", token: gtToken},
|
||||
{op: "<=", token: lteToken},
|
||||
{op: "<", token: ltToken},
|
||||
}
|
||||
|
||||
type splitted struct {
|
||||
Left []string
|
||||
Right []string
|
||||
}
|
||||
|
||||
// Find index of token in list of tokens
|
||||
func index(tokens []string, sep string) int {
|
||||
for index, token := range tokens {
|
||||
if token == sep {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Split a list of tokens by a token into a splitted struct holding the tokens
|
||||
// before and after the token to be split by.
|
||||
func splitTokens(tokens []string, sep string) (s splitted, err error) {
|
||||
index := index(tokens, sep)
|
||||
if index == -1 {
|
||||
return s, fmt.Errorf("'%s' not found in ['%s']", sep, strings.Join(tokens, "','"))
|
||||
}
|
||||
return splitted{
|
||||
Left: tokens[:index],
|
||||
Right: tokens[index+1:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Scan a string for parenthesis
|
||||
func scan(s string) <-chan match {
|
||||
ch := make(chan match)
|
||||
go func() {
|
||||
depth := 0
|
||||
opener := 0
|
||||
for index, char := range s {
|
||||
switch char {
|
||||
case '(':
|
||||
if depth == 0 {
|
||||
opener = index
|
||||
}
|
||||
depth++
|
||||
case ')':
|
||||
depth--
|
||||
if depth == 0 {
|
||||
ch <- match{
|
||||
openPos: opener,
|
||||
closePos: index + 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
// Split the string into tokens
|
||||
func split(s string) <-chan string {
|
||||
ch := make(chan string)
|
||||
go func() {
|
||||
s = strings.ReplaceAll(s, " ", "")
|
||||
if !strings.Contains(s, "(") {
|
||||
ch <- s
|
||||
} else {
|
||||
last := 0
|
||||
end := len(s)
|
||||
for info := range scan(s) {
|
||||
if last != info.openPos {
|
||||
ch <- s[last:info.openPos]
|
||||
}
|
||||
ch <- s[info.openPos:info.closePos]
|
||||
last = info.closePos
|
||||
}
|
||||
if last != end {
|
||||
ch <- s[last:]
|
||||
}
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
// Tokenizes a string into a list of strings, tokens grouped by parenthesis are
|
||||
// not split! If the string starts with ( and ends in ), those are stripped.
|
||||
func tokenize(s string) []string {
|
||||
/*
|
||||
TODO: Properly detect if the string starts with a ( and ends with a )
|
||||
and that those two form a matching pair.
|
||||
|
||||
Eg: (foo) -> true; (foo)(bar) -> false;
|
||||
*/
|
||||
if len(s) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
if s[0] == '(' && s[len(s)-1] == ')' {
|
||||
s = s[1 : len(s)-1]
|
||||
}
|
||||
ret := []string{}
|
||||
for chunk := range split(s) {
|
||||
if len(chunk) != 0 {
|
||||
if chunk[0] == '(' && chunk[len(chunk)-1] == ')' {
|
||||
ret = append(ret, chunk)
|
||||
} else {
|
||||
for _, token := range pat.FindAllStringSubmatch(chunk, -1) {
|
||||
ret = append(ret, token[0])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Empty chunk in string '%s'\n", s)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Compile a string containing a plural form expression to a Expression object.
|
||||
func Compile(s string) (expr Expression, err error) {
|
||||
if s == "0" {
|
||||
return constValue{value: 0}, nil
|
||||
}
|
||||
if !strings.Contains(s, "?") {
|
||||
s += "?1:0"
|
||||
}
|
||||
return compileExpression(s)
|
||||
}
|
||||
|
||||
// Check if a token is in a slice of strings
|
||||
func contains(haystack []string, needle string) bool {
|
||||
for _, s := range haystack {
|
||||
if s == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Compiles an expression (ternary or constant)
|
||||
func compileExpression(s string) (expr Expression, err error) {
|
||||
tokens := tokenize(s)
|
||||
if contains(tokens, "?") {
|
||||
return ternaryToken.compile(tokens)
|
||||
}
|
||||
return constToken.compile(tokens)
|
||||
}
|
||||
|
||||
// Compiles a test (comparison)
|
||||
func compileTest(s string) (test test, err error) {
|
||||
tokens := tokenize(s)
|
||||
for _, tokenDef := range precedence {
|
||||
if contains(tokens, tokenDef.op) {
|
||||
return tokenDef.token.compile(tokens)
|
||||
}
|
||||
}
|
||||
return test, errors.New("cannot compile")
|
||||
}
|
||||
|
||||
func parseUint32(s string) (ui uint32, err error) {
|
||||
i, err := strconv.ParseUint(s, 10, 32)
|
||||
if err != nil {
|
||||
return ui, err
|
||||
}
|
||||
return uint32(i), nil
|
||||
}
|
44
vendor/github.com/leonelquinteros/gotext/plurals/expression.go
generated
vendored
Normal file
44
vendor/github.com/leonelquinteros/gotext/plurals/expression.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
// Original work Copyright (c) 2016 Jonas Obrist (https://github.com/ojii/gettext.go)
|
||||
// Modified work Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com
|
||||
// Modified work Copyright (c) 2018-present gotext maintainers (https://github.com/leonelquinteros/gotext)
|
||||
//
|
||||
// Licensed under the 3-Clause BSD License. See LICENSE in the project root for license information.
|
||||
|
||||
package plurals
|
||||
|
||||
// Expression is a plurals expression. Eval evaluates the expression for
|
||||
// a given n value. Use plurals.Compile to generate Expression instances.
|
||||
type Expression interface {
|
||||
Eval(n uint32) int
|
||||
}
|
||||
|
||||
type constValue struct {
|
||||
value int
|
||||
}
|
||||
|
||||
func (c constValue) Eval(n uint32) int {
|
||||
return c.value
|
||||
}
|
||||
|
||||
type test interface {
|
||||
test(n uint32) bool
|
||||
}
|
||||
|
||||
type ternary struct {
|
||||
test test
|
||||
trueExpr Expression
|
||||
falseExpr Expression
|
||||
}
|
||||
|
||||
func (t ternary) Eval(n uint32) int {
|
||||
if t.test.test(n) {
|
||||
if t.trueExpr == nil {
|
||||
return -1
|
||||
}
|
||||
return t.trueExpr.Eval(n)
|
||||
}
|
||||
if t.falseExpr == nil {
|
||||
return -1
|
||||
}
|
||||
return t.falseExpr.Eval(n)
|
||||
}
|
41
vendor/github.com/leonelquinteros/gotext/plurals/genfixture.py
generated
vendored
Normal file
41
vendor/github.com/leonelquinteros/gotext/plurals/genfixture.py
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (c) 2016 Jonas Obrist (https://github.com/ojii/gettext.go)
|
||||
#
|
||||
# Licensed under the 3-Clause BSD License. See LICENSE in the project root for license information.
|
||||
|
||||
import json
|
||||
from gettext import c2py
|
||||
|
||||
|
||||
PLURAL_FORMS = [
|
||||
"0",
|
||||
"n!=1",
|
||||
"n>1",
|
||||
"n%10==1&&n%100!=11?0:n!=0?1:2",
|
||||
"n==1?0:n==2?1:2",
|
||||
"n==1?0:(n==0||(n%100>0&&n%100<20))?1:2",
|
||||
"n%10==1&&n%100!=11?0:n%10>=2&&(n%100<10||n%100>=20)?1:2",
|
||||
"n%10==1&&n%100!=11?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2",
|
||||
"(n==1)?0:(n>=2&&n<=4)?1:2",
|
||||
"n==1?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2",
|
||||
"n%100==1?0:n%100==2?1:n%100==3||n%100==4?2:3",
|
||||
"n==0?0:n==1?1:n==2?2:n%100>=3&&n%100<=10?3:n%100>=11?4:5",
|
||||
]
|
||||
|
||||
NUM = 1000
|
||||
|
||||
|
||||
def gen():
|
||||
tests = []
|
||||
for plural_form in PLURAL_FORMS:
|
||||
expr = c2py(plural_form)
|
||||
tests.append({
|
||||
'pluralform': plural_form,
|
||||
'fixture': [expr(n) for n in range(NUM + 1)]
|
||||
})
|
||||
return json.dumps(tests)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(gen())
|
19
vendor/github.com/leonelquinteros/gotext/plurals/math.go
generated
vendored
Normal file
19
vendor/github.com/leonelquinteros/gotext/plurals/math.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
// Original work Copyright (c) 2016 Jonas Obrist (https://github.com/ojii/gettext.go)
|
||||
// Modified work Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com
|
||||
// Modified work Copyright (c) 2018-present gotext maintainers (https://github.com/leonelquinteros/gotext)
|
||||
//
|
||||
// Licensed under the 3-Clause BSD License. See LICENSE in the project root for license information.
|
||||
|
||||
package plurals
|
||||
|
||||
type math interface {
|
||||
calc(n uint32) uint32
|
||||
}
|
||||
|
||||
type mod struct {
|
||||
value uint32
|
||||
}
|
||||
|
||||
func (m mod) calc(n uint32) uint32 {
|
||||
return n % m.value
|
||||
}
|
105
vendor/github.com/leonelquinteros/gotext/plurals/tests.go
generated
vendored
Normal file
105
vendor/github.com/leonelquinteros/gotext/plurals/tests.go
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
// Original work Copyright (c) 2016 Jonas Obrist (https://github.com/ojii/gettext.go)
|
||||
// Modified work Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com
|
||||
// Modified work Copyright (c) 2018-present gotext maintainers (https://github.com/leonelquinteros/gotext)
|
||||
//
|
||||
// Licensed under the BSD License. See License.txt in the project root for license information.
|
||||
|
||||
package plurals
|
||||
|
||||
type equal struct {
|
||||
value uint32
|
||||
}
|
||||
|
||||
func (e equal) test(n uint32) bool {
|
||||
return n == e.value
|
||||
}
|
||||
|
||||
type notequal struct {
|
||||
value uint32
|
||||
}
|
||||
|
||||
func (e notequal) test(n uint32) bool {
|
||||
return n != e.value
|
||||
}
|
||||
|
||||
type gt struct {
|
||||
value uint32
|
||||
flipped bool
|
||||
}
|
||||
|
||||
func (e gt) test(n uint32) bool {
|
||||
if e.flipped {
|
||||
return e.value > n
|
||||
}
|
||||
|
||||
return n > e.value
|
||||
}
|
||||
|
||||
type lt struct {
|
||||
value uint32
|
||||
flipped bool
|
||||
}
|
||||
|
||||
func (e lt) test(n uint32) bool {
|
||||
if e.flipped {
|
||||
return e.value < n
|
||||
}
|
||||
return n < e.value
|
||||
}
|
||||
|
||||
type gte struct {
|
||||
value uint32
|
||||
flipped bool
|
||||
}
|
||||
|
||||
func (e gte) test(n uint32) bool {
|
||||
if e.flipped {
|
||||
return e.value >= n
|
||||
}
|
||||
return n >= e.value
|
||||
}
|
||||
|
||||
type lte struct {
|
||||
value uint32
|
||||
flipped bool
|
||||
}
|
||||
|
||||
func (e lte) test(n uint32) bool {
|
||||
if e.flipped {
|
||||
return e.value <= n
|
||||
}
|
||||
return n <= e.value
|
||||
}
|
||||
|
||||
type and struct {
|
||||
left test
|
||||
right test
|
||||
}
|
||||
|
||||
func (e and) test(n uint32) bool {
|
||||
if !e.left.test(n) {
|
||||
return false
|
||||
}
|
||||
return e.right.test(n)
|
||||
}
|
||||
|
||||
type or struct {
|
||||
left test
|
||||
right test
|
||||
}
|
||||
|
||||
func (e or) test(n uint32) bool {
|
||||
if e.left.test(n) {
|
||||
return true
|
||||
}
|
||||
return e.right.test(n)
|
||||
}
|
||||
|
||||
type pipe struct {
|
||||
modifier math
|
||||
action test
|
||||
}
|
||||
|
||||
func (e pipe) test(n uint32) bool {
|
||||
return e.action.test(e.modifier.calc(n))
|
||||
}
|
427
vendor/github.com/leonelquinteros/gotext/po.go
generated
vendored
Normal file
427
vendor/github.com/leonelquinteros/gotext/po.go
generated
vendored
Normal file
@ -0,0 +1,427 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Po parses the content of any PO file and provides all the Translation functions needed.
|
||||
It's the base object used by all package methods.
|
||||
And it's safe for concurrent use by multiple goroutines by using the sync package for locking.
|
||||
|
||||
Example:
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/leonelquinteros/gotext"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create po object
|
||||
po := gotext.NewPo()
|
||||
|
||||
// Parse .po file
|
||||
po.ParseFile("/path/to/po/file/translations.po")
|
||||
|
||||
// Get Translation
|
||||
fmt.Println(po.Get("Translate this"))
|
||||
}
|
||||
*/
|
||||
type Po struct {
|
||||
// these three public members are for backwards compatibility. they are just set to the value in the domain
|
||||
Headers HeaderMap
|
||||
Language string
|
||||
PluralForms string
|
||||
|
||||
domain *Domain
|
||||
fs fs.FS
|
||||
}
|
||||
|
||||
type parseState int
|
||||
|
||||
const (
|
||||
head parseState = iota
|
||||
msgCtxt
|
||||
msgID
|
||||
msgIDPlural
|
||||
msgStr
|
||||
)
|
||||
|
||||
// NewPo should always be used to instantiate a new Po object
|
||||
func NewPo() *Po {
|
||||
po := new(Po)
|
||||
po.domain = NewDomain()
|
||||
|
||||
return po
|
||||
}
|
||||
|
||||
// NewPoFS works like NewPO but adds an optional fs.FS
|
||||
func NewPoFS(filesystem fs.FS) *Po {
|
||||
po := NewPo()
|
||||
po.fs = filesystem
|
||||
return po
|
||||
}
|
||||
|
||||
// GetDomain returns the domain object
|
||||
func (po *Po) GetDomain() *Domain {
|
||||
return po.domain
|
||||
}
|
||||
|
||||
// Convenience interfaces
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// DropStaleTranslations removes all translations that are not referenced in the current domain
|
||||
func (po *Po) DropStaleTranslations() {
|
||||
po.domain.DropStaleTranslations()
|
||||
}
|
||||
|
||||
// SetRefs sets the references for a given translation
|
||||
func (po *Po) SetRefs(str string, refs []string) {
|
||||
po.domain.SetRefs(str, refs)
|
||||
}
|
||||
|
||||
// GetRefs returns the references for a given translation
|
||||
func (po *Po) GetRefs(str string) []string {
|
||||
return po.domain.GetRefs(str)
|
||||
}
|
||||
|
||||
// SetPluralResolver sets the plural resolver function
|
||||
func (po *Po) SetPluralResolver(f func(int) int) {
|
||||
po.domain.customPluralResolver = f
|
||||
}
|
||||
|
||||
// Set translation
|
||||
func (po *Po) Set(id, str string) {
|
||||
po.domain.Set(id, str)
|
||||
}
|
||||
|
||||
// Get translation
|
||||
func (po *Po) Get(str string, vars ...interface{}) string {
|
||||
return po.domain.Get(str, vars...)
|
||||
}
|
||||
|
||||
// Append translation
|
||||
func (po *Po) Append(b []byte, str string, vars ...interface{}) []byte {
|
||||
return po.domain.Append(b, str, vars...)
|
||||
}
|
||||
|
||||
// SetN sets the plural translation
|
||||
func (po *Po) SetN(id, plural string, n int, str string) {
|
||||
po.domain.SetN(id, plural, n, str)
|
||||
}
|
||||
|
||||
// GetN gets the plural translation
|
||||
func (po *Po) GetN(str, plural string, n int, vars ...interface{}) string {
|
||||
return po.domain.GetN(str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// AppendN appends the plural translation
|
||||
func (po *Po) AppendN(b []byte, str, plural string, n int, vars ...interface{}) []byte {
|
||||
return po.domain.AppendN(b, str, plural, n, vars...)
|
||||
}
|
||||
|
||||
// SetC sets the translation for a given context
|
||||
func (po *Po) SetC(id, ctx, str string) {
|
||||
po.domain.SetC(id, ctx, str)
|
||||
}
|
||||
|
||||
// GetC gets the translation for a given context
|
||||
func (po *Po) GetC(str, ctx string, vars ...interface{}) string {
|
||||
return po.domain.GetC(str, ctx, vars...)
|
||||
}
|
||||
|
||||
// AppendC appends the translation for a given context
|
||||
func (po *Po) AppendC(b []byte, str, ctx string, vars ...interface{}) []byte {
|
||||
return po.domain.AppendC(b, str, ctx, vars...)
|
||||
}
|
||||
|
||||
// SetNC sets the plural translation for a given context
|
||||
func (po *Po) SetNC(id, plural, ctx string, n int, str string) {
|
||||
po.domain.SetNC(id, plural, ctx, n, str)
|
||||
}
|
||||
|
||||
// GetNC gets the plural translation for a given context
|
||||
func (po *Po) GetNC(str, plural string, n int, ctx string, vars ...interface{}) string {
|
||||
return po.domain.GetNC(str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// AppendNC appends the plural translation for a given context
|
||||
func (po *Po) AppendNC(b []byte, str, plural string, n int, ctx string, vars ...interface{}) []byte {
|
||||
return po.domain.AppendNC(b, str, plural, n, ctx, vars...)
|
||||
}
|
||||
|
||||
// IsTranslated checks if the given string is translated
|
||||
func (po *Po) IsTranslated(str string) bool {
|
||||
return po.domain.IsTranslated(str)
|
||||
}
|
||||
|
||||
// IsTranslatedN checks if the given string is translated with plural form
|
||||
func (po *Po) IsTranslatedN(str string, n int) bool {
|
||||
return po.domain.IsTranslatedN(str, n)
|
||||
}
|
||||
|
||||
// IsTranslatedC checks if the given string is translated with context
|
||||
func (po *Po) IsTranslatedC(str, ctx string) bool {
|
||||
return po.domain.IsTranslatedC(str, ctx)
|
||||
}
|
||||
|
||||
// IsTranslatedNC checks if the given string is translated with plural form and context
|
||||
func (po *Po) IsTranslatedNC(str string, n int, ctx string) bool {
|
||||
return po.domain.IsTranslatedNC(str, n, ctx)
|
||||
}
|
||||
|
||||
// MarshalText marshals the Po object to text
|
||||
func (po *Po) MarshalText() ([]byte, error) {
|
||||
return po.domain.MarshalText()
|
||||
}
|
||||
|
||||
// MarshalBinary marshals the Po object to binary
|
||||
func (po *Po) MarshalBinary() ([]byte, error) {
|
||||
return po.domain.MarshalBinary()
|
||||
}
|
||||
|
||||
// UnmarshalBinary unmarshals the Po object from binary
|
||||
func (po *Po) UnmarshalBinary(data []byte) error {
|
||||
return po.domain.UnmarshalBinary(data)
|
||||
}
|
||||
|
||||
// ParseFile loads the translations from a file
|
||||
func (po *Po) ParseFile(f string) {
|
||||
data, err := getFileData(f, po.fs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
po.Parse(data)
|
||||
}
|
||||
|
||||
// Parse loads the translations specified in the provided byte slice (buf)
|
||||
func (po *Po) Parse(buf []byte) {
|
||||
if po.domain == nil {
|
||||
panic("NewPo() was not used to instantiate this object")
|
||||
}
|
||||
|
||||
// Lock while parsing
|
||||
po.domain.trMutex.Lock()
|
||||
po.domain.pluralMutex.Lock()
|
||||
defer po.domain.trMutex.Unlock()
|
||||
defer po.domain.pluralMutex.Unlock()
|
||||
|
||||
// Get lines
|
||||
lines := strings.Split(string(buf), "\n")
|
||||
|
||||
// Init buffer
|
||||
po.domain.trBuffer = NewTranslation()
|
||||
po.domain.ctxBuffer = ""
|
||||
po.domain.refBuffer = ""
|
||||
|
||||
state := head
|
||||
for _, l := range lines {
|
||||
// Trim spaces
|
||||
l = strings.TrimSpace(l)
|
||||
|
||||
// Skip invalid lines
|
||||
if !po.isValidLine(l) {
|
||||
po.parseComment(l, state)
|
||||
continue
|
||||
}
|
||||
|
||||
// Buffer context and continue
|
||||
if strings.HasPrefix(l, "msgctxt") {
|
||||
po.parseContext(l)
|
||||
state = msgCtxt
|
||||
continue
|
||||
}
|
||||
|
||||
// Buffer msgid and continue
|
||||
if strings.HasPrefix(l, "msgid") && !strings.HasPrefix(l, "msgid_plural") {
|
||||
po.parseID(l)
|
||||
state = msgID
|
||||
continue
|
||||
}
|
||||
|
||||
// Check for plural form
|
||||
if strings.HasPrefix(l, "msgid_plural") {
|
||||
po.parsePluralID(l)
|
||||
po.domain.pluralTranslations[po.domain.trBuffer.PluralID] = po.domain.trBuffer
|
||||
state = msgIDPlural
|
||||
continue
|
||||
}
|
||||
|
||||
// Save Translation
|
||||
if strings.HasPrefix(l, "msgstr") {
|
||||
po.parseMessage(l)
|
||||
state = msgStr
|
||||
continue
|
||||
}
|
||||
|
||||
// Multi line strings and headers
|
||||
if strings.HasPrefix(l, "\"") && strings.HasSuffix(l, "\"") {
|
||||
po.parseString(l, state)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Save last Translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Parse headers
|
||||
po.domain.parseHeaders()
|
||||
|
||||
// set values on this struct
|
||||
// this is for backwards compatibility
|
||||
po.Language = po.domain.Language
|
||||
po.PluralForms = po.domain.PluralForms
|
||||
po.Headers = po.domain.Headers
|
||||
}
|
||||
|
||||
// saveBuffer takes the context and Translation buffers
|
||||
// and saves it on the translations collection
|
||||
func (po *Po) saveBuffer() {
|
||||
// With no context...
|
||||
if po.domain.ctxBuffer == "" {
|
||||
po.domain.translations[po.domain.trBuffer.ID] = po.domain.trBuffer
|
||||
} else {
|
||||
// With context...
|
||||
if _, ok := po.domain.contextTranslations[po.domain.ctxBuffer]; !ok {
|
||||
po.domain.contextTranslations[po.domain.ctxBuffer] = make(map[string]*Translation)
|
||||
}
|
||||
po.domain.contextTranslations[po.domain.ctxBuffer][po.domain.trBuffer.ID] = po.domain.trBuffer
|
||||
|
||||
// Cleanup current context buffer if needed
|
||||
if po.domain.trBuffer.ID != "" {
|
||||
po.domain.ctxBuffer = ""
|
||||
}
|
||||
}
|
||||
|
||||
// Flush Translation buffer
|
||||
if po.domain.refBuffer == "" {
|
||||
po.domain.trBuffer = NewTranslation()
|
||||
} else {
|
||||
po.domain.trBuffer = NewTranslationWithRefs(strings.Split(po.domain.refBuffer, " "))
|
||||
}
|
||||
}
|
||||
|
||||
// Either preserves comments before the first "msgid", for later round-trip.
|
||||
// Or preserves source references for a given translation.
|
||||
func (po *Po) parseComment(l string, state parseState) {
|
||||
if len(l) > 0 && l[0] == '#' {
|
||||
if state == head {
|
||||
po.domain.headerComments = append(po.domain.headerComments, l)
|
||||
} else if len(l) > 1 {
|
||||
switch l[1] {
|
||||
case ':':
|
||||
if len(l) > 2 {
|
||||
po.domain.refBuffer = strings.TrimSpace(l[2:])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseContext takes a line starting with "msgctxt",
|
||||
// saves the current Translation buffer and creates a new context.
|
||||
func (po *Po) parseContext(l string) {
|
||||
// Save current Translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Buffer context
|
||||
po.domain.ctxBuffer, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgctxt")))
|
||||
}
|
||||
|
||||
// parseID takes a line starting with "msgid",
|
||||
// saves the current Translation and creates a new msgid buffer.
|
||||
func (po *Po) parseID(l string) {
|
||||
// Save current Translation buffer.
|
||||
po.saveBuffer()
|
||||
|
||||
// Set id
|
||||
po.domain.trBuffer.ID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid")))
|
||||
}
|
||||
|
||||
// parsePluralID saves the plural id buffer from a line starting with "msgid_plural"
|
||||
func (po *Po) parsePluralID(l string) {
|
||||
po.domain.trBuffer.PluralID, _ = strconv.Unquote(strings.TrimSpace(strings.TrimPrefix(l, "msgid_plural")))
|
||||
}
|
||||
|
||||
// parseMessage takes a line starting with "msgstr" and saves it into the current buffer.
|
||||
func (po *Po) parseMessage(l string) {
|
||||
l = strings.TrimSpace(strings.TrimPrefix(l, "msgstr"))
|
||||
|
||||
// Check for indexed Translation forms
|
||||
if strings.HasPrefix(l, "[") {
|
||||
idx := strings.Index(l, "]")
|
||||
if idx == -1 {
|
||||
// Skip wrong index formatting
|
||||
return
|
||||
}
|
||||
|
||||
// Parse index
|
||||
i, err := strconv.Atoi(l[1:idx])
|
||||
if err != nil {
|
||||
// Skip wrong index formatting
|
||||
return
|
||||
}
|
||||
|
||||
// Parse Translation string
|
||||
po.domain.trBuffer.Trs[i], _ = strconv.Unquote(strings.TrimSpace(l[idx+1:]))
|
||||
|
||||
// Loop
|
||||
return
|
||||
}
|
||||
|
||||
// Save single Translation form under 0 index
|
||||
po.domain.trBuffer.Trs[0], _ = strconv.Unquote(l)
|
||||
}
|
||||
|
||||
// parseString takes a well formatted string without prefix
|
||||
// and creates headers or attach multi-line strings when corresponding
|
||||
func (po *Po) parseString(l string, state parseState) {
|
||||
clean, _ := strconv.Unquote(l)
|
||||
|
||||
switch state {
|
||||
case msgStr:
|
||||
// Append to last Translation found
|
||||
po.domain.trBuffer.Trs[len(po.domain.trBuffer.Trs)-1] += clean
|
||||
|
||||
case msgID:
|
||||
// Multiline msgid - Append to current id
|
||||
po.domain.trBuffer.ID += clean
|
||||
|
||||
case msgIDPlural:
|
||||
// Multiline msgid - Append to current id
|
||||
po.domain.trBuffer.PluralID += clean
|
||||
|
||||
case msgCtxt:
|
||||
// Multiline context - Append to current context
|
||||
po.domain.ctxBuffer += clean
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// isValidLine checks for line prefixes to detect valid syntax.
|
||||
func (po *Po) isValidLine(l string) bool {
|
||||
// Check prefix
|
||||
valid := []string{
|
||||
"\"",
|
||||
"msgctxt",
|
||||
"msgid",
|
||||
"msgid_plural",
|
||||
"msgstr",
|
||||
}
|
||||
|
||||
for _, v := range valid {
|
||||
if strings.HasPrefix(l, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
97
vendor/github.com/leonelquinteros/gotext/translation.go
generated
vendored
Normal file
97
vendor/github.com/leonelquinteros/gotext/translation.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
// Translation is the struct for the Translations parsed via Po or Mo files and all coming parsers
|
||||
type Translation struct {
|
||||
ID string
|
||||
PluralID string
|
||||
Trs map[int]string
|
||||
Refs []string
|
||||
|
||||
dirty bool
|
||||
}
|
||||
|
||||
// NewTranslation returns the Translation object and initialized it.
|
||||
func NewTranslation() *Translation {
|
||||
return &Translation{
|
||||
Trs: make(map[int]string),
|
||||
}
|
||||
}
|
||||
|
||||
// NewTranslationWithRefs returns the Translation object and initialized it with references.
|
||||
func NewTranslationWithRefs(refs []string) *Translation {
|
||||
return &Translation{
|
||||
Trs: make(map[int]string),
|
||||
Refs: refs,
|
||||
}
|
||||
}
|
||||
|
||||
// IsStale returns whether the translation is stale or not
|
||||
func (t *Translation) IsStale() bool {
|
||||
return !t.dirty
|
||||
}
|
||||
|
||||
// SetRefs sets the references of the translation
|
||||
func (t *Translation) SetRefs(refs []string) {
|
||||
t.Refs = refs
|
||||
t.dirty = true
|
||||
}
|
||||
|
||||
// Set sets the string of the translation
|
||||
func (t *Translation) Set(str string) {
|
||||
t.Trs[0] = str
|
||||
t.dirty = true
|
||||
}
|
||||
|
||||
// Get returns the string of the translation
|
||||
func (t *Translation) Get() string {
|
||||
// Look for Translation index 0
|
||||
if _, ok := t.Trs[0]; ok {
|
||||
if t.Trs[0] != "" {
|
||||
return t.Trs[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Return untranslated id by default
|
||||
return t.ID
|
||||
}
|
||||
|
||||
// SetN sets the string of the plural translation
|
||||
func (t *Translation) SetN(n int, str string) {
|
||||
t.Trs[n] = str
|
||||
t.dirty = true
|
||||
}
|
||||
|
||||
// GetN returns the string of the plural translation
|
||||
func (t *Translation) GetN(n int) string {
|
||||
// Look for Translation index
|
||||
if _, ok := t.Trs[n]; ok {
|
||||
if t.Trs[n] != "" {
|
||||
return t.Trs[n]
|
||||
}
|
||||
}
|
||||
|
||||
// Return untranslated singular if corresponding
|
||||
if n == 0 {
|
||||
return t.ID
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
96
vendor/github.com/leonelquinteros/gotext/translator.go
generated
vendored
Normal file
96
vendor/github.com/leonelquinteros/gotext/translator.go
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 2018 DeineAgentur UG https://www.deineagentur.com. All rights reserved.
|
||||
* Licensed under the MIT License. See LICENSE file in the project root for full license information.
|
||||
*/
|
||||
|
||||
package gotext
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Translator interface is used by Locale and Po objects.Translator
|
||||
// It contains all methods needed to parse translation sources and obtain corresponding translations.
|
||||
// Also implements gob.GobEncoder/gob.DobDecoder interfaces to allow serialization of Locale objects.
|
||||
type Translator interface {
|
||||
ParseFile(f string)
|
||||
Parse(buf []byte)
|
||||
Get(str string, vars ...interface{}) string
|
||||
GetN(str, plural string, n int, vars ...interface{}) string
|
||||
GetC(str, ctx string, vars ...interface{}) string
|
||||
GetNC(str, plural string, n int, ctx string, vars ...interface{}) string
|
||||
|
||||
MarshalBinary() ([]byte, error)
|
||||
UnmarshalBinary([]byte) error
|
||||
GetDomain() *Domain
|
||||
}
|
||||
|
||||
// AppendTranslator interface is used by Locale and Po objects.
|
||||
// It contains all methods needed to parse translation sources and to append entries to the object.
|
||||
type AppendTranslator interface {
|
||||
Translator
|
||||
Append(b []byte, str string, vars ...interface{}) []byte
|
||||
AppendN(b []byte, str, plural string, n int, vars ...interface{}) []byte
|
||||
AppendC(b []byte, str, ctx string, vars ...interface{}) []byte
|
||||
AppendNC(b []byte, str, plural string, n int, ctx string, vars ...interface{}) []byte
|
||||
}
|
||||
|
||||
// TranslatorEncoding is used as intermediary storage to encode Translator objects to Gob.
|
||||
type TranslatorEncoding struct {
|
||||
// Headers storage
|
||||
Headers HeaderMap
|
||||
|
||||
// Language header
|
||||
Language string
|
||||
|
||||
// Plural-Forms header
|
||||
PluralForms string
|
||||
|
||||
// Parsed Plural-Forms header values
|
||||
Nplurals int
|
||||
Plural string
|
||||
|
||||
// Storage
|
||||
Translations map[string]*Translation
|
||||
Contexts map[string]map[string]*Translation
|
||||
}
|
||||
|
||||
// GetTranslator is used to recover a Translator object after unmarshalling the TranslatorEncoding object.
|
||||
// Internally uses a Po object as it should be switchable with Mo objects without problem.
|
||||
// External Translator implementations should be able to serialize into a TranslatorEncoding object in order to
|
||||
// deserialize into a Po-compatible object.
|
||||
func (te *TranslatorEncoding) GetTranslator() Translator {
|
||||
po := NewPo()
|
||||
po.domain = NewDomain()
|
||||
po.domain.Headers = te.Headers
|
||||
po.domain.Language = te.Language
|
||||
po.domain.PluralForms = te.PluralForms
|
||||
po.domain.nplurals = te.Nplurals
|
||||
po.domain.plural = te.Plural
|
||||
po.domain.translations = te.Translations
|
||||
po.domain.contextTranslations = te.Contexts
|
||||
|
||||
return po
|
||||
}
|
||||
|
||||
// getFileData reads a file and returns the byte slice after doing some basic sanity checking
|
||||
func getFileData(f string, filesystem fs.FS) ([]byte, error) {
|
||||
if filesystem != nil {
|
||||
return fs.ReadFile(filesystem, f)
|
||||
}
|
||||
|
||||
// Check if file exists
|
||||
info, err := os.Stat(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check that isn't a directory
|
||||
if info.IsDir() {
|
||||
return nil, errors.New("cannot parse a directory")
|
||||
}
|
||||
|
||||
return os.ReadFile(f)
|
||||
}
|
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
@ -404,6 +404,10 @@ github.com/klauspost/compress/internal/le
|
||||
github.com/klauspost/compress/internal/snapref
|
||||
github.com/klauspost/compress/zstd
|
||||
github.com/klauspost/compress/zstd/internal/xxhash
|
||||
# github.com/leonelquinteros/gotext v1.7.2
|
||||
## explicit; go 1.23.5
|
||||
github.com/leonelquinteros/gotext
|
||||
github.com/leonelquinteros/gotext/plurals
|
||||
# github.com/lucasb-eyer/go-colorful v1.2.0
|
||||
## explicit; go 1.12
|
||||
github.com/lucasb-eyer/go-colorful
|
||||
@ -464,8 +468,6 @@ github.com/muesli/ansi/compressor
|
||||
# github.com/muesli/cancelreader v0.2.2
|
||||
## explicit; go 1.17
|
||||
github.com/muesli/cancelreader
|
||||
# github.com/muesli/reflow v0.3.0
|
||||
## explicit; go 1.13
|
||||
# github.com/muesli/termenv v0.16.0
|
||||
## explicit; go 1.17
|
||||
github.com/muesli/termenv
|
||||
|
Reference in New Issue
Block a user