Skip to content

Commit 6314bcc

Browse files
authored
Merge pull request #5 from donseba/tweaks
Tweaks and language file creation
2 parents 11b1bf1 + 9320a59 commit 6314bcc

File tree

10 files changed

+4175
-97
lines changed

10 files changed

+4175
-97
lines changed

README.md

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,50 @@ Localizer interface {
8282
```
8383
8484
85-
Adding a New Language
85+
Setting a language to use
8686
--
8787
```go
88-
tr.AddLanguage("en_US")
89-
tr.AddLanguage("nl_NL")
88+
tr.SetLanguage("en_US")
89+
tr.SetLanguage("nl_NL")
9090
```
9191
92+
Setting and possibly creating a New Language
93+
---------------------
94+
95+
To add or ensure a language file exists (and is loaded), use the `EnsureLanguage` method. This will create a new `.po` file with the correct header (including plural forms) if it does not exist, and then load it into the translator:
96+
97+
```go
98+
err := tr.EnsureLanguage("fr") // creates fr.po if missing, with correct header
99+
if err != nil {
100+
log.Fatal(err)
101+
}
102+
```
103+
104+
- The generated `.po` file will always include the recommended headers:
105+
- `Content-Type: text/plain; charset=UTF-8`
106+
- `Content-Transfer-Encoding: 8bit`
107+
- `Plural-Forms` (auto-filled from plural rules)
108+
- `Language` (set to the language code)
109+
110+
- Calling `EnsureLanguage` multiple times is safe and will not overwrite existing files or translations.
111+
112+
Testing and Idempotency
113+
-----------------------
114+
115+
- The package includes tests to ensure that language files are created with the correct headers and that repeated calls to `EnsureLanguage` do not overwrite existing files.
116+
- Plural rules are generated from `plurals.json` and included in the codebase for accuracy and maintainability.
117+
118+
Updating Plural Rules
119+
---------------------
120+
121+
If you update `plurals.json`, regenerate the plural rules Go map by running:
122+
123+
```sh
124+
go run tools/generate_templates.go
125+
```
126+
127+
This will update `generated_plural_templates.go` with the latest plural forms and language codes.
128+
92129
Scanning for Missing Translations
93130
--
94131
To check for missing translations in your templates:
@@ -108,6 +145,8 @@ tr.SetPrefixSeparator("__CUSTOM__")
108145
```
109146
The default prefix separator is `__.`
110147
148+
> **Note/Disclaimer:** While customizing the prefix separator is supported, it is generally recommended to use the CTL or CTN methods instead. These methods allow you to set translation context explicitly, which is more robust and flexible for handling similar keys in different contexts.
149+
111150
Removing Prefixes from Translations
112151
--
113152
The package automatically handles the removal of prefixes from translations at runtime:

file_templates.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package translator
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"time"
8+
)
9+
10+
// POT/PO file header template struct
11+
// You can expand this struct as needed for more fields
12+
// See: https://www.gnu.org/software/gettext/manual/html_node/Header-Entry.html
13+
14+
type TranslationFileHeader struct {
15+
ProjectID string
16+
ReportMsgidBugsTo string
17+
POTCreationDate string
18+
PORevisionDate string
19+
LastTranslator string
20+
LanguageTeam string
21+
Language string
22+
MIMEVersion string
23+
ContentType string
24+
ContentTransferEncoding string
25+
PluralForms string
26+
}
27+
28+
// DefaultHeader returns a default header for a given language
29+
func DefaultHeader(language string) TranslationFileHeader {
30+
now := time.Now().Format("2006-01-02 15:04-0700")
31+
return TranslationFileHeader{
32+
ProjectID: "PACKAGE VERSION",
33+
ReportMsgidBugsTo: "",
34+
POTCreationDate: now,
35+
PORevisionDate: now,
36+
LastTranslator: "",
37+
LanguageTeam: "",
38+
Language: language,
39+
MIMEVersion: "1.0",
40+
ContentType: "text/plain; charset=UTF-8",
41+
ContentTransferEncoding: "8bit",
42+
PluralForms: "nplurals=2; plural=(n != 1);",
43+
}
44+
}
45+
46+
// GetHeaderForLanguage returns a header template for a given language, falling back to DefaultHeader
47+
func GetHeaderForLanguage(language string) TranslationFileHeader {
48+
if h, ok := LanguageHeaderTemplates[language]; ok {
49+
h.ProjectID = "Go-FORM"
50+
h.POTCreationDate = time.Now().Format("2006-01-02 15:04-0700")
51+
h.PORevisionDate = h.POTCreationDate
52+
h.LanguageTeam = "Go-FORM"
53+
h.Language = language
54+
h.ContentType = "text/plain; charset=UTF-8"
55+
h.ContentTransferEncoding = "8bit"
56+
return h
57+
}
58+
return DefaultHeader(language)
59+
}
60+
61+
// WritePOTFile creates a POT file with the default header if it does not exist
62+
func WritePOTFile(path string) error {
63+
if _, err := os.Stat(path); err == nil {
64+
return nil // file exists
65+
}
66+
h := DefaultHeader("")
67+
f, err := os.Create(path)
68+
if err != nil {
69+
return err
70+
}
71+
defer f.Close()
72+
_, err = f.WriteString(h.HeaderString())
73+
return err
74+
}
75+
76+
// WritePOFile creates a PO file for a language with the default header if it does not exist
77+
func WritePOFile(path, language string) error {
78+
if _, err := os.Stat(path); err == nil {
79+
return nil // file exists
80+
}
81+
h := DefaultHeader(language)
82+
f, err := os.Create(path)
83+
if err != nil {
84+
return err
85+
}
86+
defer f.Close()
87+
_, err = f.WriteString(h.HeaderString())
88+
return err
89+
}
90+
91+
// HeaderString returns the formatted header for a PO/POT file
92+
func (h TranslationFileHeader) HeaderString() string {
93+
// Ensure Language is always set and not empty
94+
lang := h.Language
95+
if lang == "" {
96+
lang = "C"
97+
}
98+
return fmt.Sprintf(`msgid ""
99+
msgstr ""
100+
"Project-Id-Version: %s\n"
101+
"Report-Msgid-Bugs-To: %s\n"
102+
"POT-Creation-Date: %s\n"
103+
"PO-Revision-Date: %s\n"
104+
"Last-Translator: %s\n"
105+
"Language-Team: %s\n"
106+
"Language: %s\n"
107+
"MIME-Version: %s\n"
108+
"Content-Type: %s\n"
109+
"Content-Transfer-Encoding: %s\n"
110+
"Plural-Forms: %s\n"
111+
`,
112+
h.ProjectID,
113+
h.ReportMsgidBugsTo,
114+
h.POTCreationDate,
115+
h.PORevisionDate,
116+
h.LastTranslator,
117+
h.LanguageTeam,
118+
lang,
119+
h.MIMEVersion,
120+
h.ContentType,
121+
h.ContentTransferEncoding,
122+
h.PluralForms,
123+
)
124+
}
125+
126+
// GenerateLanguageHeaderTemplatesFromJSON parses a plurals.json file and generates Go code for LanguageHeaderTemplates
127+
func GenerateLanguageHeaderTemplatesFromJSON(jsonPath, goPath string) error {
128+
data, err := os.ReadFile(jsonPath)
129+
if err != nil {
130+
return err
131+
}
132+
var plurals map[string]struct {
133+
Name string `json:"name"`
134+
Formulas struct {
135+
Standard string `json:"standard"`
136+
PHP string `json:"php"`
137+
} `json:"formulas"`
138+
Plurals int `json:"plurals"`
139+
}
140+
if err := json.Unmarshal(data, &plurals); err != nil {
141+
return err
142+
}
143+
144+
out := "package translator\n\n// Code generated by script. DO NOT EDIT.\n\nvar LanguageHeaderTemplates = map[string]TranslationFileHeader{\n"
145+
for code, v := range plurals {
146+
pluralForms := "nplurals=" + itoa(v.Plurals) + "; plural=(" + v.Formulas.Standard + ");"
147+
out += "\t\"" + code + "\": {PluralForms: \"" + pluralForms + "\", Language: \"" + code + "\"},\n"
148+
}
149+
out += "}\n"
150+
return os.WriteFile(goPath, []byte(out), 0644)
151+
}
152+
153+
func itoa(i int) string {
154+
return fmt.Sprintf("%d", i)
155+
}

0 commit comments

Comments
 (0)