├── .env
├── .gitignore
├── date_formats.json
├── README.md
├── locales
├── default.po
├── el_GR
│ └── LC_MESSAGES
│ │ └── default.po
├── ar_SA
│ └── LC_MESSAGES
│ │ └── default.po
└── en_US
│ └── LC_MESSAGES
│ └── default.po
├── pkg
├── model
│ └── speedrun.go
├── i18n
│ ├── helpers.go
│ ├── lang.go
│ └── i18n.go
└── handlers
│ └── handlers.go
├── go.mod
├── scripts
├── init-locales.sh
└── extract-strings.sh
├── static
├── stylesheet.css
├── index.html
└── speedrun.html
├── main.go
└── go.sum
/.env:
--------------------------------------------------------------------------------
1 | LANGUAGE=el_GR
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | .idea
3 | .phrase.yml
4 | .env
5 | cache
--------------------------------------------------------------------------------
/date_formats.json:
--------------------------------------------------------------------------------
1 | {
2 | "en_US": "01/02/2006 03:04:05 PM",
3 | "el_GR": "02/01/2006 15:04:05",
4 | "ar_SA": "02/01/2006 15:04:05"
5 | }
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | go-internationalization - PhraseApp.com
2 | ---
3 | Simple example of Go i18n App using `golang.org/x/text` and `leonelquinteros/gotext`
4 |
--------------------------------------------------------------------------------
/locales/default.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Project-Id-Version: go-internationalization 0.1.0\n"
4 | "Last-Translator: Theo Despoudis\n"
5 | "Language: en_US\n"
6 | "MIME-Version: 1.0\n"
7 | "Content-Type: text/plain; charset=UTF-8\n"
8 | "Content-Transfer-Encoding: 8bit\n"
9 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
--------------------------------------------------------------------------------
/pkg/model/speedrun.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import "time"
4 |
5 | type Speedrun struct {
6 | PlayerName string `json:"player_name"`
7 | Game string `json:"game"`
8 | Category string `json:"category"`
9 | Time string `json:"time"`
10 | SubmittedAt time.Time `json:"submitted_at"`
11 | }
12 |
--------------------------------------------------------------------------------
/pkg/i18n/helpers.go:
--------------------------------------------------------------------------------
1 | package i18n
2 |
3 | import (
4 | "os"
5 | "path"
6 | )
7 |
8 | func getLocalePath() (string, error) {
9 | rootPath, err := getPwdDirPath()
10 | if err != nil {
11 | return "", err
12 | }
13 | return path.Join(rootPath, "locales"), nil
14 | }
15 |
16 | func getPwdDirPath() (string, error) {
17 | rootPath, err := os.Getwd()
18 | if err != nil {
19 | return "", err
20 | }
21 | return rootPath, nil
22 | }
23 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module PhraseApp-Blog/go-internationalization
2 |
3 | go 1.22.0
4 |
5 | require (
6 | github.com/leonelquinteros/gotext v1.5.2
7 | golang.org/x/text v0.14.0
8 | )
9 |
10 | require (
11 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
12 | github.com/joho/godotenv v1.5.1 // indirect
13 | github.com/maximilien/i18n4go v0.6.0 // indirect
14 | github.com/spf13/cobra v1.7.0 // indirect
15 | github.com/spf13/pflag v1.0.5 // indirect
16 | )
17 |
--------------------------------------------------------------------------------
/scripts/init-locales.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | initialize_locale_directories() {
4 | # Iterate over each provided language code
5 | for lang_code in "$@"; do
6 | # Define locale directory path
7 | locale_dir="locales/$lang_code/LC_MESSAGES"
8 |
9 | # Create the locale directory if it doesn't exist
10 | mkdir -p "$locale_dir"
11 |
12 | # Initialize the default.po file using msginit
13 | msginit --no-translator -o "$locale_dir/default.po" \
14 | -l "$lang_code" -i "locales/default.po"
15 |
16 | echo "Initialized locale directory"
17 | echo "for $lang_code"
18 | done
19 | }
20 |
21 | if [ $# -eq 0 ]; then
22 | echo "Error: No language codes provided."
23 | echo "Please provide one or more language codes as arguments."
24 | exit 1
25 | fi
26 |
27 | initialize_locale_directories "$@"
--------------------------------------------------------------------------------
/pkg/i18n/lang.go:
--------------------------------------------------------------------------------
1 | package i18n
2 |
3 | import (
4 | "github.com/leonelquinteros/gotext"
5 | )
6 |
7 | // LanguageCode represents a language code.
8 | type LanguageCode string
9 |
10 | // Constants representing supported language codes.
11 | const (
12 | GR LanguageCode = "el_GR" // Greek language code
13 | EN LanguageCode = "en_US" // English language code
14 | AR LanguageCode = "ar_SA" // Arabic language code
15 | )
16 |
17 | // langMap stores Locale instances for each language code.
18 | var langMap = make(map[LanguageCode]*gotext.Locale)
19 |
20 | // LanguageDirectionMap maps language codes to their typical text directions
21 | var LanguageDirectionMap = map[LanguageCode]string{
22 | "el_GR": "ltr", // Greek language code
23 | "en_US": "ltr", // English language code
24 | "ar_SA": "rtl", // Arabic language code
25 | }
26 |
27 | // String returns the string representation of a LanguageCode.
28 | func (l LanguageCode) String() string {
29 | return string(l)
30 | }
31 |
32 | // T returns the translated string for the given key in the specified language.
33 | func (l LanguageCode) T(s string) string {
34 | // Check if a Locale exists for the specified language code
35 | if lang, ok := langMap[l]; ok {
36 | // Retrieve the translated string from the Locale
37 | return lang.Get(s)
38 | }
39 | // Return the original string if no translation is available
40 | return s
41 | }
42 |
--------------------------------------------------------------------------------
/static/stylesheet.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Arial, sans-serif;
3 | background-color: #f0f0f0;
4 | margin: 0;
5 | padding: 0;
6 | }
7 | .container {
8 | max-width: 64rem;
9 | margin: 20px auto;
10 | padding: 20px;
11 | }
12 | h1,
13 | h2,
14 | h3,
15 | h4 {
16 | color: #333;
17 | }
18 | ul {
19 | list-style-type: none;
20 | padding: 0;
21 | }
22 | li {
23 | margin-bottom: 10px;
24 | padding: 10px;
25 | background-color: #f9f9f9;
26 | border-radius: 8px;
27 | }
28 | form {
29 | margin-top: 20px;
30 | }
31 | label {
32 | display: block;
33 | font-weight: bold;
34 | margin-bottom: 5px;
35 | }
36 | input[type="text"],
37 | button {
38 | padding: 10px;
39 | font-size: 16px;
40 | border: 1px solid #ccc;
41 | border-radius: 5px;
42 | margin-bottom: 10px;
43 | }
44 | button {
45 | background-color: #4caf50;
46 | color: white;
47 | cursor: pointer;
48 | transition: background-color 0.3s;
49 | }
50 | button:hover {
51 | background-color: #45a049;
52 | }
53 |
54 | h1, h2, form {
55 | margin-top: 20px;
56 | color: #333;
57 | }
58 |
59 | #leaderboard {
60 | list-style-type: none;
61 | padding: 0;
62 | max-width: 600px;
63 | margin: 20px auto;
64 | }
65 |
66 | li {
67 | margin-bottom: 10px;
68 | padding: 10px;
69 | background-color: #f9f9f9;
70 | border-radius: 8px;
71 | }
72 | table {
73 | border-collapse: collapse;
74 | background-color: #fff;
75 | }
76 |
77 | th,
78 | td {
79 | padding: 8px;
80 | text-align: left;
81 | border-bottom: 1px solid #ddd;
82 | }
83 |
84 | th {
85 | background-color: #f2f2f2;
86 | }
87 |
--------------------------------------------------------------------------------
/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{T .Title}}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
{{T .Header}}
22 |
23 |
24 | | {{T .PlayerName}} |
25 | {{T .Game}} |
26 | {{T .Category}} |
27 | {{T .Time}} |
28 | {{T .Date}} |
29 |
30 | {{range .Data}}
31 |
32 | | {{.PlayerName}} |
33 | {{.Game}} |
34 | {{.Category}} |
35 | {{.Time}} |
36 | {{FormatLocalizedDate .SubmittedAt $.CurrentLanguage}} |
37 |
38 | {{end}}
39 |
40 |
{{TN "EntryAdded" "EntriesAdded" 5}}
41 |
42 |
43 |
44 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "PhraseApp-Blog/go-internationalization/pkg/handlers"
5 | "PhraseApp-Blog/go-internationalization/pkg/i18n"
6 | "PhraseApp-Blog/go-internationalization/pkg/model"
7 | "fmt"
8 | "log"
9 |
10 | "encoding/json"
11 | "net/http"
12 | "time"
13 | )
14 |
15 | // Initialize sample data
16 | var speedruns = []model.Speedrun{
17 | {PlayerName: "Alex", Game: "Super Mario 64", Category: "Any%", Time: "16:58", SubmittedAt: time.Now()},
18 | {PlayerName: "Theo", Game: "The Legend of Zelda: Ocarina of Time", Category: "Any%", Time: "1:20:41", SubmittedAt: time.Now()},
19 | }
20 |
21 | func main() {
22 | // Initialize i18n package
23 | if err := i18n.Init(); err != nil {
24 | log.Fatalf("failed to initialize i18n: %v", err)
25 | }
26 |
27 | // Define routes
28 | http.Handle("/", detectLanguageMiddleware(handleIndex))
29 | http.HandleFunc("/speedruns", handleSpeedruns)
30 | http.HandleFunc("/speedruns/add", detectLanguageMiddleware(handleSpeedrunForm))
31 | http.HandleFunc("/speedrun.html", detectLanguageMiddleware(handleSpeedrunForm))
32 |
33 | // Serve static files
34 | http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
35 |
36 | // Start server
37 | fmt.Println(i18n.T("Server listening on port %d...", 8080))
38 | http.ListenAndServe(":8080", nil)
39 | }
40 |
41 | func handleIndex(w http.ResponseWriter, r *http.Request) {
42 | handlers.HandleIndex(w, r, speedruns)
43 | }
44 |
45 | func handleSpeedruns(w http.ResponseWriter, r *http.Request) {
46 | // Send speedrun data as JSON response
47 | w.Header().Set("Content-Type", "application/json")
48 | json.NewEncoder(w).Encode(speedruns)
49 | }
50 |
51 | func handleSpeedrunForm(w http.ResponseWriter, r *http.Request) {
52 | if r.Method == http.MethodPost {
53 | // Parse request body to get new speedrun data
54 | var speedrun model.Speedrun
55 | err := json.NewDecoder(r.Body).Decode(&speedrun)
56 | if err != nil {
57 | http.Error(w, err.Error(), http.StatusBadRequest)
58 | return
59 | }
60 |
61 | // Add the current date as the submitted date
62 | speedrun.SubmittedAt = time.Now()
63 |
64 | // Add new speedrun to the global speedruns slice
65 | speedruns = append(speedruns, speedrun)
66 |
67 | // Send success response
68 | fmt.Fprintln(w, i18n.T("Speedrun submitted successfully!"))
69 | } else {
70 | handlers.HandleSpeedrun(w, r)
71 | }
72 | }
73 |
74 | func detectLanguageMiddleware(next http.HandlerFunc) http.HandlerFunc {
75 | return func(w http.ResponseWriter, r *http.Request) {
76 | // Get the preferred locale based on the request's Accept-Language header
77 | lang := i18n.DetectPreferredLocale(r)
78 | i18n.SetCurrentLocale(lang)
79 | next.ServeHTTP(w, r)
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/locales/el_GR/LC_MESSAGES/default.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Language: el_GR\n"
4 | "MIME-Version: 1.0\n"
5 | "Content-Type: text/plain; charset=UTF-8\n"
6 | "Content-Transfer-Encoding: 8bit\n"
7 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
8 | "X-Generator: Phrase (phrase.com)\n"
9 |
10 | # filename: pkg/handlers/handlers.go, offset: 2209, line: 69, column: 25
11 | msgid "Submit"
12 | msgstr "Submit"
13 |
14 | # filename: pkg/handlers/handlers.go, offset: 795, line: 25, column: 16
15 | msgid "static/"
16 | msgstr "static/"
17 |
18 | # filename: pkg/handlers/handlers.go, offset: 1442, line: 49, column: 25
19 | msgid "Speedrun Leaderboard"
20 | msgstr "Speedrun Leaderboard"
21 |
22 | # filename: pkg/handlers/handlers.go, offset: 2102, line: 66, column: 25
23 | msgid "Player Name"
24 | msgstr "Όνομα Παίχτη"
25 |
26 | # filename: pkg/handlers/handlers.go, offset: 2173, line: 68, column: 25
27 | msgid "Category"
28 | msgstr "Category"
29 |
30 | # filename: pkg/handlers/handlers.go, offset: 2243, line: 70, column: 25
31 | msgid "Time"
32 | msgstr "Time"
33 |
34 | # filename: pkg/handlers/handlers.go, offset: 2058, line: 65, column: 25
35 | msgid "Add New Speedrun"
36 | msgstr "Add New Speedrun"
37 |
38 | # filename: pkg/handlers/handlers.go, offset: 1332, line: 47, column: 23
39 | msgid "index.html"
40 | msgstr "index.html"
41 |
42 | # filename: pkg/handlers/handlers.go, offset: 2141, line: 67, column: 25
43 | msgid "Game"
44 | msgstr "Game"
45 |
46 | # filename: pkg/handlers/handlers.go, offset: 1629, line: 54, column: 25
47 | msgid "Submitted At"
48 | msgstr "Submitted At"
49 |
50 | # filename: pkg/handlers/handlers.go, offset: 1949, line: 63, column: 23
51 | msgid "speedrun.html"
52 | msgstr "speedrun.html"
53 |
54 | # filename: pkg/handlers/handlers.go, offset: 2870, line: 84, column: 41
55 | msgid "lang"
56 | msgstr "lang"
57 |
58 | # filename: pkg/i18n/i18n.go, offset: 1225, line: 60, column: 21
59 | msgid "LC_ALL"
60 | msgstr "LC_ALL"
61 |
62 | # filename: pkg/i18n/i18n.go, offset: 1437, line: 68, column: 19
63 | msgid "LANG"
64 | msgstr "LANG"
65 |
66 | # filename: pkg/i18n/i18n.go, offset: 2454, line: 108, column: 35
67 | msgid "el"
68 | msgstr "el"
69 |
70 | # filename: pkg/i18n/i18n.go, offset: 2873, line: 128, column: 33
71 | msgid "Accept-Language"
72 | msgstr "Accept-Language"
73 |
74 | msgid "Category"
75 | msgstr "Κατηγορία"
76 |
77 | msgid "Time"
78 | msgstr "Χρόνος"
79 |
80 | msgid "Submit"
81 | msgstr "Υποβολλή"
82 |
83 | msgid "Game"
84 | msgstr "Παιχνίδι"
85 |
86 | msgid "Submitted At"
87 | msgstr "Υποβλήθηκε:"
88 |
89 | msgid "Add New Speedrun"
90 | msgstr "Προσθήκη νέου Speedrun"
91 |
92 | msgid "Speedrun Leaderboard"
93 | msgstr "Κορυφαίοι χρόνοι"
94 |
95 | msgid "Select Language"
96 | msgstr "Επιλέξτε γλώσσα"
97 |
98 | msgid "el_GR"
99 | msgstr "Ελληνικά"
100 |
101 | msgid "en_US"
102 | msgstr "Αγγλικά"
103 |
104 | msgid "ar_SA"
105 | msgstr "Αραβικά"
106 |
107 | msgid "EntryAdded"
108 | msgid_plural "EntriesAdded"
109 | msgstr[0] "%d νέα καταχώριση προστέθηκε σήμερα"
110 | msgstr[1] "%d νέες καταχωρήσεις σήμερα"
111 | msgstr[2] "Καμία καταχωρήση σήμερα"
--------------------------------------------------------------------------------
/scripts/extract-strings.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Set up directories relative to the current directory
4 | workdir="cache"
5 | localesdir="locales"
6 | ignored="^(vendor|resources|cache|catalog|$workdir)"
7 |
8 | # Function to extract messages
9 | extract_messages() {
10 | if [ -e "$workdir" ]; then
11 | rm -rf "$workdir"
12 | fi
13 | mkdir "$workdir"
14 |
15 | # The following flags are:
16 |
17 | # -c extract-strings: This flag specifies the action to perform, which in this case is to extract translation strings from the source code.
18 | # -po: This flag indicates that the output format should be .po files.
19 | # -d: This flag specifies the directory to search for Go source code files. Here, . represents the current directory.
20 | # r: This flag tells the tool to search for Go source code files recursively within the specified directory.
21 | # -o "$workdir": This flag specifies the output directory where the extracted translation strings will be saved. The $workdir variable contains the path to the working directory, where the extracted strings will be stored.
22 | # —ignore-regexp "$ignored": This flag specifies a regular expression pattern to ignore certain files or directories during the extraction process. The $ignored variable contains the pattern for files or directories to ignore.
23 | # —output-match-package: This flag indicates that the extracted translation strings should be grouped by package.
24 | i18n4go -c extract-strings -v --po -d . -r -o "$workdir" --ignore-regexp "$ignored" -output-match-package
25 | }
26 |
27 | # Function to merge messages for all locales into .pot files
28 | merge_messages() {
29 | # Merge all .po files into a single .po file
30 | msgcat --use-first "$workdir"/**/*.po -o "$workdir/merged_messages.po"
31 |
32 | # Add charset specification to the merged .po file
33 | echo 'msgid ""' > "$workdir/merged_messages_with_charset.po"
34 | echo 'msgstr ""' >> "$workdir/merged_messages_with_charset.po"
35 | echo '"Content-Type: text/plain; charset=UTF-8\n"' >> "$workdir/merged_messages_with_charset.po"
36 | cat "$workdir/merged_messages.po" >> "$workdir/merged_messages_with_charset.po"
37 |
38 | # Iterate over each subdirectory in the locales directory
39 | for locale_dir in "$localesdir"/*; do
40 | # Check if it's a directory
41 | if [ -d "$locale_dir" ]; then
42 | # Extract the locale from the directory name
43 | locale=$(basename "$locale_dir")
44 | # Check if there are any .po files in the LC_MESSAGES directory of the locale
45 | if compgen -G "$locale_dir/LC_MESSAGES/default.po" > /dev/null; then
46 | # Merge the combined .po file into the individual locale .po file without overriding existing translations
47 | msgcat --use-first "$locale_dir/LC_MESSAGES/default.po" "$workdir/merged_messages_with_charset.po" -o "$locale_dir/LC_MESSAGES/default_merged.po"
48 | # Move the merged .po file to replace the original default.po file
49 | mv "$locale_dir/LC_MESSAGES/default_merged.po" "$locale_dir/LC_MESSAGES/default.po"
50 | else
51 | echo "No default.po file found in $locale_dir/LC_MESSAGES. Skipping merging for $locale."
52 | fi
53 | fi
54 | done
55 |
56 | # Clean up temporary files
57 | rm -f "$workdir/merged_messages.po" "$workdir/merged_messages_with_charset.po"
58 | }
59 |
60 | # Main script
61 | if [ $# -gt 0 ]; then
62 | if [ "$1" = "--extract" ]; then
63 | extract_messages
64 | merge_messages
65 | fi
66 | fi
--------------------------------------------------------------------------------
/static/speedrun.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Add New Speedrun
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
{{T .Header}}
22 |
37 |
38 |
39 |
76 |
77 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
2 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
3 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
4 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
5 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
6 | github.com/leonelquinteros/gotext v1.5.2 h1:T2y6ebHli+rMBCjcJlHTXyUrgXqsKBhl/ormgvt7lPo=
7 | github.com/leonelquinteros/gotext v1.5.2/go.mod h1:AT4NpQrOmyj1L/+hLja6aR0lk81yYYL4ePnj2kp7d6M=
8 | github.com/maximilien/i18n4go v0.6.0 h1:YSUVdzPwMc3aTtwmwBSD0iyIkKDD+t9/Awbf/WBiT9E=
9 | github.com/maximilien/i18n4go v0.6.0/go.mod h1:k2ysiHUTkx5DnOPyUfEf37H7yiY0t0t1gj8CPBWkdwg=
10 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
11 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
12 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
13 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
14 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
15 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
16 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
17 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
18 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
19 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
20 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
21 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
22 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
23 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
24 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
25 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
26 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
27 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
28 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
29 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
30 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
31 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
32 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
33 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
34 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
35 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
36 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
37 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
38 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
39 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
40 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
41 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
42 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
43 |
--------------------------------------------------------------------------------
/pkg/handlers/handlers.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "PhraseApp-Blog/go-internationalization/pkg/i18n"
5 | "PhraseApp-Blog/go-internationalization/pkg/model"
6 | "context"
7 | "fmt"
8 | "html/template"
9 | "net/http"
10 | "time"
11 |
12 | "golang.org/x/text/language"
13 | )
14 |
15 | func HandleTemplate(w http.ResponseWriter, r *http.Request,
16 | tmplName string, data interface{}) {
17 | // Parse the template
18 | tmpl, err := template.New(tmplName).Funcs(template.FuncMap{
19 | "T": i18n.T,
20 | "TN": i18n.TN,
21 | "FormatLocalizedDate": func(submittedAt time.Time,
22 | currentLanguage i18n.LanguageCode) string {
23 | return i18n.FormatLocalizedDate(submittedAt, currentLanguage.String())
24 | },
25 | "FormatNumber": func(number int64, currentLanguage i18n.LanguageCode) string {
26 | return i18n.FormatNumber(number, language.Make(currentLanguage.String()))
27 | },
28 | }).ParseFiles("static/" + tmplName)
29 | if err != nil {
30 | http.Error(w, err.Error(), http.StatusInternalServerError)
31 | return
32 | }
33 | // Execute the template with the translation function and data
34 | err = tmpl.Execute(w, data)
35 | if err != nil {
36 | http.Error(w, err.Error(), http.StatusInternalServerError)
37 | return
38 | }
39 | }
40 |
41 | type FormattedSpeedrun struct {
42 | PlayerName string
43 | Game string
44 | Category string
45 | Time string
46 | SubmittedAt string
47 | }
48 |
49 | type TemplateData struct {
50 | Header string
51 | Title string
52 | PlayerName string
53 | Game string
54 | Category string
55 | Time string
56 | Date string
57 | Submit string
58 | Data []model.Speedrun
59 | }
60 |
61 | func HandleIndex(w http.ResponseWriter, r *http.Request, data []model.Speedrun) {
62 | fmt.Println(i18n.GetCurrentLanguage())
63 | HandleTemplate(w, r, "index.html", map[string]interface{}{
64 | "Title": "Speedrun Leaderboard",
65 | "Header": "Speedrun Leaderboard",
66 | "PlayerName": "Player Name",
67 | "Game": "Game",
68 | "Category": "Category",
69 | "Time": "Time",
70 | "Date": "Submitted At",
71 | "Data": data,
72 | "Dir": i18n.LanguageDirectionMap[i18n.GetCurrentLanguage()],
73 | "CurrentLanguage": i18n.GetCurrentLanguage(),
74 | "SupportedLanguages": i18n.GetSupportedLanguages(),
75 | })
76 | }
77 |
78 | func HandleSpeedrun(w http.ResponseWriter, r *http.Request) {
79 | HandleTemplate(w, r, "speedrun.html", map[string]interface{}{
80 | "Header": "Add New Speedrun",
81 | "Title": "Add New Speedrun",
82 | "PlayerName": "Player Name",
83 | "Game": "Game",
84 | "Category": "Category",
85 | "Submit": "Submit",
86 | "Time": "Time",
87 | "Dir": i18n.LanguageDirectionMap[i18n.GetCurrentLanguage()],
88 | "CurrentLanguage": i18n.GetCurrentLanguage(),
89 | "SupportedLanguages": i18n.GetSupportedLanguages(),
90 | })
91 | }
92 |
93 | // Middleware to set the current language in the context of the request
94 | func SetCurrentLanguage(next http.Handler) http.Handler {
95 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
96 | // Get the language from the request (you may need to adjust this based on your implementation)
97 | lang := r.URL.Query().Get("lang")
98 |
99 | // Set the language in the context of the request
100 | ctx := context.WithValue(r.Context(), "lang", lang)
101 |
102 | // Call the next handler in the chain with the new context
103 | next.ServeHTTP(w, r.WithContext(ctx))
104 | })
105 | }
106 |
107 | // Use this starter code to map page names to template data.
108 | // Then on each handler instead of hardcoding the page template data, you use the mergeTemplateData function to merge the page template data with the extra data you want to pass to the template.
109 |
110 | // var pageTemplateData map[string]map[string]interface{}
111 |
112 | // func mergeTemplateData(page string, extraData interface{}) map[string]interface{} {
113 | // data := pageTemplateData[page]
114 | // data["Data"] = extraData
115 | // data["Dir"] = i18n.LanguageDirectionMap[i18n.GetCurrentLanguage()]
116 | // data["CurrentLanguage"] = i18n.GetCurrentLanguage()
117 | // data["SupportedLanguages"] = i18n.GetSupportedLanguages()
118 | // return data
119 | // }
120 |
--------------------------------------------------------------------------------
/locales/ar_SA/LC_MESSAGES/default.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Language: ar_SA\n"
4 | "MIME-Version: 1.0\n"
5 | "Content-Type: text/plain; charset=UTF-8\n"
6 | "Content-Transfer-Encoding: 8bit\n"
7 | "Plural-Forms: nplurals=6; plural= n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
8 | "X-Generator: Phrase (phrase.com)\n"
9 |
10 | # filename: pkg/handlers/handlers.go, offset: 2209, line: 69, column: 25
11 | msgid "Submit"
12 | msgstr "Submit"
13 |
14 | # filename: pkg/handlers/handlers.go, offset: 795, line: 25, column: 16
15 | msgid "static/"
16 | msgstr "static/"
17 |
18 | # filename: pkg/handlers/handlers.go, offset: 1442, line: 49, column: 25
19 | msgid "Speedrun Leaderboard"
20 | msgstr "Speedrun Leaderboard"
21 |
22 | # filename: pkg/handlers/handlers.go, offset: 2102, line: 66, column: 25
23 | msgid "Player Name"
24 | msgstr "Player Name"
25 |
26 | # filename: pkg/handlers/handlers.go, offset: 2173, line: 68, column: 25
27 | msgid "Category"
28 | msgstr "Category"
29 |
30 | # filename: pkg/handlers/handlers.go, offset: 2243, line: 70, column: 25
31 | msgid "Time"
32 | msgstr "Time"
33 |
34 | # filename: pkg/handlers/handlers.go, offset: 2058, line: 65, column: 25
35 | msgid "Add New Speedrun"
36 | msgstr "Add New Speedrun"
37 |
38 | # filename: pkg/handlers/handlers.go, offset: 1332, line: 47, column: 23
39 | msgid "index.html"
40 | msgstr "index.html"
41 |
42 | # filename: pkg/handlers/handlers.go, offset: 2141, line: 67, column: 25
43 | msgid "Game"
44 | msgstr "Game"
45 |
46 | # filename: pkg/handlers/handlers.go, offset: 1629, line: 54, column: 25
47 | msgid "Submitted At"
48 | msgstr "Submitted At"
49 |
50 | # filename: pkg/handlers/handlers.go, offset: 1949, line: 63, column: 23
51 | msgid "speedrun.html"
52 | msgstr "speedrun.html"
53 |
54 | # filename: pkg/handlers/handlers.go, offset: 2870, line: 84, column: 41
55 | msgid "lang"
56 | msgstr "lang"
57 |
58 | # filename: pkg/i18n/i18n.go, offset: 1225, line: 60, column: 21
59 | msgid "LC_ALL"
60 | msgstr "LC_ALL"
61 |
62 | # filename: pkg/i18n/i18n.go, offset: 1437, line: 68, column: 19
63 | msgid "LANG"
64 | msgstr "LANG"
65 |
66 | # filename: pkg/i18n/i18n.go, offset: 2454, line: 108, column: 35
67 | msgid "el"
68 | msgstr "el"
69 |
70 | # filename: pkg/i18n/i18n.go, offset: 2873, line: 128, column: 33
71 | msgid "Accept-Language"
72 | msgstr "Accept-Language"
73 |
74 | # filename: pkg/i18n/i18n.go, offset: 4051, line: 166, column: 19
75 | msgid "02/01/2006 15:04:05"
76 | msgstr "02/01/2006 15:04:05"
77 |
78 | # filename: pkg/i18n/i18n.go, offset: 5110, line: 202, column: 19
79 | msgid "%d"
80 | msgstr "%d"
81 |
82 | # filename: pkg/i18n/i18n.go, offset: 228, line: 19, column: 18
83 | msgid "default"
84 | msgstr "default"
85 |
86 | # filename: pkg/i18n/i18n.go, offset: 1128, line: 56, column: 21
87 | msgid "LANGUAGE"
88 | msgstr "LANGUAGE"
89 |
90 | # filename: pkg/i18n/i18n.go, offset: 1325, line: 64, column: 21
91 | msgid "LC_MESSAGES"
92 | msgstr "LC_MESSAGES"
93 |
94 | # filename: pkg/i18n/i18n.go, offset: 3015, line: 133, column: 10
95 | msgid "en"
96 | msgstr "en"
97 |
98 | # filename: pkg/i18n/i18n.go, offset: 3695, line: 155, column: 46
99 | msgid "date_formats.json"
100 | msgstr "date_formats.json"
101 |
102 | # filename: pkg/i18n/i18n.go, offset: 379, line: 27, column: 29
103 | msgid "locales"
104 | msgstr "locales"
105 |
106 | # filename: pkg/i18n/i18n.go, offset: 794, line: 47, column: 14
107 | msgid "languageCode:"
108 | msgstr "languageCode:"
109 |
110 | # filename: pkg/i18n/lang.go, offset: 314, line: 14, column: 20
111 | msgid "ar_SA"
112 | msgstr "ar_SA"
113 |
114 | # filename: pkg/i18n/lang.go, offset: 640, line: 23, column: 11
115 | msgid "ltr"
116 | msgstr "ltr"
117 |
118 | # filename: pkg/i18n/lang.go, offset: 682, line: 24, column: 11
119 | msgid "rtl"
120 | msgstr "rtl"
121 |
122 | # filename: pkg/i18n/lang.go, offset: 212, line: 12, column: 20
123 | msgid "el_GR"
124 | msgstr "el_GR"
125 |
126 | # filename: pkg/i18n/lang.go, offset: 262, line: 13, column: 20
127 | msgid "en_US"
128 | msgstr "en_US"
129 |
130 | # filename: main.go, offset: 2592, line: 99, column: 29
131 | msgid "Super Mario 64"
132 | msgstr "Super Mario 64"
133 |
134 | # filename: main.go, offset: 2761, line: 100, column: 93
135 | msgid "1:20:41"
136 | msgstr "1:20:41"
137 |
138 | # filename: main.go, offset: 3005, line: 111, column: 18
139 | msgid "/speedruns"
140 | msgstr "/speedruns"
141 |
142 | # filename: main.go, offset: 3108, line: 113, column: 18
143 | msgid "/speedrun.html"
144 | msgstr "/speedrun.html"
145 |
146 | # filename: main.go, offset: 3364, line: 120, column: 22
147 | msgid ":8080"
148 | msgstr ":8080"
149 |
150 | # filename: main.go, offset: 2578, line: 99, column: 15
151 | msgid "Alex"
152 | msgstr "Alex"
153 |
154 | # filename: main.go, offset: 2697, line: 100, column: 29
155 | msgid "The Legend of Zelda: Ocarina of Time"
156 | msgstr "The Legend of Zelda: Ocarina of Time"
157 |
158 | # filename: main.go, offset: 2893, line: 106, column: 14
159 | msgid "failed to initialize i18n: %v"
160 | msgstr "failed to initialize i18n: %v"
161 |
162 | # filename: main.go, offset: 3053, line: 112, column: 18
163 | msgid "/speedruns/add"
164 | msgstr "/speedruns/add"
165 |
166 | # filename: main.go, offset: 3212, line: 116, column: 43
167 | msgid "/static/"
168 | msgstr "/static/"
169 |
170 | msgid "Speedrun Leaderboard"
171 | msgstr "لوحة نتائج السرعة"
172 |
173 | msgid "Player Name"
174 | msgstr "اسم اللاعب"
175 |
176 | msgid "Category"
177 | msgstr "الفئة"
178 |
179 | msgid "Time"
180 | msgstr "الزمن"
181 |
182 | msgid "Submit"
183 | msgstr "تقديم"
184 |
185 | msgid "Game"
186 | msgstr "اللعبة"
187 |
188 | msgid "Submitted At"
189 | msgstr "تم التقديم في:"
190 |
191 | msgid "Add New Speedrun"
192 | msgstr "إضافة سباق سريع جديد"
193 |
194 | msgid "Select Language"
195 | msgstr "اختار اللغة"
196 |
197 | msgid "el_GR"
198 | msgstr "اليونانية"
199 |
200 | msgid "en_US"
201 | msgstr "الإنجليزية"
202 |
203 | msgid "ar_SA"
204 | msgstr "العربية"
205 |
206 | msgid "EntryAdded"
207 | msgid_plural "EntriesAdded"
208 | msgstr[0] "لم تتم إضافة أي إدخالات جديدة اليوم"
209 | msgstr[1] "تمت إضافة إدخال %d جديد اليوم"
210 | msgstr[2] "تمت إضافة مدخلين %d جديدين اليوم"
211 | msgstr[3] "تمت إضافة %d إدخالاً جديدًا اليوم"
212 | msgstr[4] "تمت إضافة مدخلين %d جديدين اليوم"
213 | msgstr[5] "تمت إضافة %d إدخالات جديدة اليوم"
214 |
--------------------------------------------------------------------------------
/pkg/i18n/i18n.go:
--------------------------------------------------------------------------------
1 | package i18n
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | "os"
8 | "strings"
9 | "time"
10 |
11 | "github.com/leonelquinteros/gotext"
12 | "golang.org/x/text/language"
13 | "golang.org/x/text/message"
14 | )
15 |
16 | var (
17 | defaultDomain = "default"
18 | )
19 |
20 | func Init() error {
21 | localePath, err := getLocalePath()
22 | if err != nil {
23 | return err
24 | }
25 | languageCode := getLanguageCode()
26 | fullLocale := NewLanguageFromString(languageCode).String()
27 | gotext.Configure(localePath, fullLocale, defaultDomain)
28 | setupLocales(localePath)
29 | fmt.Println("languageCode:", localePath)
30 |
31 | return nil
32 | }
33 |
34 | // GetLanguageCode returns the language code from environment variables LANGUAGE, LC_ALL, or LC_MESSAGES,
35 | // in that order of priority. It returns an empty string if none of the variables are set.
36 | func getLanguageCode() string {
37 | // Check LANGUAGE environment variable
38 | if lc := os.Getenv("LANGUAGE"); lc != "" {
39 | return lc
40 | }
41 | // Check LC_ALL environment variable
42 | if lc := os.Getenv("LC_ALL"); lc != "" {
43 | return lc
44 | }
45 | // Check LC_MESSAGES environment variable
46 | if lc := os.Getenv("LC_MESSAGES"); lc != "" {
47 | return lc
48 | }
49 | // No language code found in environment variables
50 | return os.Getenv("LANG")
51 | }
52 |
53 | func setupLocales(localePath string) error {
54 | // Get a list of all directories in the locale path
55 | localeDirs, err := os.ReadDir(localePath)
56 | if err != nil {
57 | return err
58 | }
59 |
60 | // Iterate over each directory and add it as a supported language
61 | for _, dir := range localeDirs {
62 | if dir.IsDir() {
63 | langCode := LanguageCode(dir.Name())
64 | lang := gotext.NewLocale(localePath, langCode.String())
65 | lang.AddDomain(defaultDomain)
66 | langMap[langCode] = lang
67 | }
68 | }
69 |
70 | return nil
71 | }
72 |
73 | // GetSupportedLanguages returns the list of supported language codes.
74 | func GetSupportedLanguages() []LanguageCode {
75 | // Initialize an empty slice to store language codes
76 | var languages []LanguageCode
77 |
78 | // Iterate over the keys of langMap and collect them into the slice
79 | for lang := range langMap {
80 | languages = append(languages, lang)
81 | }
82 |
83 | return languages
84 | }
85 |
86 | func NewLanguageFromString(code string) LanguageCode {
87 | code = strings.ToLower(code)
88 | if strings.Contains(code, "en") {
89 | return EN
90 | } else if strings.Contains(code, "el") {
91 | return GR
92 | }
93 | return AR
94 | }
95 |
96 | func T(s string, args ...interface{}) string {
97 | return gotext.Get(s, args...)
98 | }
99 |
100 | func TN(s string, p string, n int, args ...interface{}) string {
101 | return gotext.GetN(s, p, n, n)
102 | }
103 | func GetCurrentLanguage() LanguageCode {
104 | // Get the current language code from the gotext package
105 | lang := gotext.GetLanguage()
106 |
107 | // Convert the language code to LanguageCode enum
108 | return LanguageCode(lang)
109 | }
110 |
111 | func DetectPreferredLocale(r *http.Request) string {
112 | // Check if lang parameter is provided in the URL
113 | langParam := LanguageCode(r.URL.Query().Get("lang"))
114 | if langParam != "" {
115 | // Check if the provided lang parameter is supported
116 | for _, supportedLang := range GetSupportedLanguages() {
117 | if langParam == supportedLang {
118 | return langParam.String()
119 | }
120 | }
121 | }
122 |
123 | // Get Accept-Language header value
124 | acceptLanguage := r.Header.Get("Accept-Language")
125 |
126 | // Parse Accept-Language header
127 | prefs, _, err := language.ParseAcceptLanguage(acceptLanguage)
128 | if err != nil {
129 | // Default to English if parsing fails
130 | return "en_US"
131 | }
132 |
133 | // Convert supported language codes to language.Tags
134 | var supportedTags []language.Tag
135 | for _, code := range GetSupportedLanguages() {
136 | tag := language.Make(code.String())
137 | supportedTags = append(supportedTags, tag)
138 | }
139 |
140 | // Find the best match between supported languages and client preferences
141 | match := language.NewMatcher(supportedTags)
142 | _, index, _ := match.Match(prefs...)
143 |
144 | // Get the best match language
145 | locale := GetSupportedLanguages()[index]
146 |
147 | return locale.String()
148 | }
149 |
150 | // SetCurrentLocale sets the current locale based on the language code
151 | func SetCurrentLocale(lang string) {
152 | // If the language parameter is provided, set the current locale
153 | if lang != "" {
154 | // Get the preferred locale based on the language code
155 | locale := (lang)
156 |
157 | // Set the current locale
158 | gotext.SetLanguage(locale)
159 | }
160 | }
161 |
162 | func FormatLocalizedDate(t time.Time, lang string) string {
163 | // Read date formats from JSON file
164 | dateFormats, err := readDateFormatsFromFile("date_formats.json")
165 | if err != nil {
166 | // Log or handle error
167 | // Fallback to default format if unable to read formats
168 | return t.Format("02/01/2006 15:04:05")
169 | }
170 |
171 | // Get the appropriate date format for the given language
172 | format, ok := dateFormats[lang]
173 | if !ok {
174 | // If the language is not recognized, use a default format
175 | return t.Format("02/01/2006 15:04:05")
176 | }
177 |
178 | // Load the appropriate time location based on the language tag
179 | loc, err := time.LoadLocation(lang)
180 | if err != nil {
181 | // Log or handle error
182 | // Fallback to default location if an error occurs
183 | loc = time.UTC
184 | }
185 | fmt.Println(format)
186 | // Format the time using the specified format and location
187 | return t.In(loc).Format(format)
188 | }
189 |
190 | // readDateFormatsFromFile reads date formats from a JSON file.
191 | func readDateFormatsFromFile(filename string) (map[string]string, error) {
192 | data, err := os.ReadFile(filename)
193 | if err != nil {
194 | return nil, err
195 | }
196 |
197 | var dateFormats map[string]string
198 | if err := json.Unmarshal(data, &dateFormats); err != nil {
199 | return nil, err
200 | }
201 |
202 | return dateFormats, nil
203 | }
204 |
205 | // FormatNumber formats the given number according to the specified language locale.
206 | func FormatNumber(number int64, lang language.Tag) string {
207 |
208 | // Create a new message printer with the specified language
209 | p := message.NewPrinter(lang)
210 | // Format the number with grouping separators according to the user's preferred language
211 | return p.Sprintf("%d", number)
212 | }
213 |
--------------------------------------------------------------------------------
/locales/en_US/LC_MESSAGES/default.po:
--------------------------------------------------------------------------------
1 | msgid ""
2 | msgstr ""
3 | "Language: en_US\n"
4 | "MIME-Version: 1.0\n"
5 | "Content-Type: text/plain; charset=UTF-8\n"
6 | "Content-Transfer-Encoding: 8bit\n"
7 | "Plural-Forms: nplurals=2; plural=(n != 1);\n"
8 | "X-Generator: Phrase (phrase.com)\n"
9 |
10 | # filename: pkg/handlers/handlers.go, offset: 2209, line: 69, column: 25
11 | msgid "Submit"
12 | msgstr "Submit"
13 |
14 | # filename: pkg/handlers/handlers.go, offset: 795, line: 25, column: 16
15 | msgid "static/"
16 | msgstr "static/"
17 |
18 | # filename: pkg/handlers/handlers.go, offset: 1442, line: 49, column: 25
19 | msgid "Speedrun Leaderboard"
20 | msgstr "Speedrun Leaderboard"
21 |
22 | # filename: pkg/handlers/handlers.go, offset: 2102, line: 66, column: 25
23 | msgid "Player Name"
24 | msgstr "Player Name"
25 |
26 | # filename: pkg/handlers/handlers.go, offset: 2173, line: 68, column: 25
27 | msgid "Category"
28 | msgstr "Category"
29 |
30 | # filename: pkg/handlers/handlers.go, offset: 2243, line: 70, column: 25
31 | msgid "Time"
32 | msgstr "Time"
33 |
34 | # filename: pkg/handlers/handlers.go, offset: 2058, line: 65, column: 25
35 | msgid "Add New Speedrun"
36 | msgstr "Add New Speedrun"
37 |
38 | # filename: pkg/handlers/handlers.go, offset: 1332, line: 47, column: 23
39 | msgid "index.html"
40 | msgstr "index.html"
41 |
42 | # filename: pkg/handlers/handlers.go, offset: 2141, line: 67, column: 25
43 | msgid "Game"
44 | msgstr "Game"
45 |
46 | # filename: pkg/handlers/handlers.go, offset: 1629, line: 54, column: 25
47 | msgid "Submitted At"
48 | msgstr "Submitted At"
49 |
50 | # filename: pkg/handlers/handlers.go, offset: 1949, line: 63, column: 23
51 | msgid "speedrun.html"
52 | msgstr "speedrun.html"
53 |
54 | # filename: pkg/handlers/handlers.go, offset: 2870, line: 84, column: 41
55 | msgid "lang"
56 | msgstr "lang"
57 |
58 | # filename: pkg/i18n/i18n.go, offset: 1225, line: 60, column: 21
59 | msgid "LC_ALL"
60 | msgstr "LC_ALL"
61 |
62 | # filename: pkg/i18n/i18n.go, offset: 1437, line: 68, column: 19
63 | msgid "LANG"
64 | msgstr "LANG"
65 |
66 | # filename: pkg/i18n/i18n.go, offset: 2454, line: 108, column: 35
67 | msgid "el"
68 | msgstr "el"
69 |
70 | # filename: pkg/i18n/i18n.go, offset: 2873, line: 128, column: 33
71 | msgid "Accept-Language"
72 | msgstr "Accept-Language"
73 |
74 | # filename: pkg/i18n/i18n.go, offset: 4051, line: 166, column: 19
75 | msgid "02/01/2006 15:04:05"
76 | msgstr "02/01/2006 15:04:05"
77 |
78 | # filename: pkg/i18n/i18n.go, offset: 5110, line: 202, column: 19
79 | msgid "%d"
80 | msgstr "%d"
81 |
82 | # filename: pkg/i18n/i18n.go, offset: 228, line: 19, column: 18
83 | msgid "default"
84 | msgstr "default"
85 |
86 | # filename: pkg/i18n/i18n.go, offset: 1128, line: 56, column: 21
87 | msgid "LANGUAGE"
88 | msgstr "LANGUAGE"
89 |
90 | # filename: pkg/i18n/i18n.go, offset: 1325, line: 64, column: 21
91 | msgid "LC_MESSAGES"
92 | msgstr "LC_MESSAGES"
93 |
94 | # filename: pkg/i18n/i18n.go, offset: 3015, line: 133, column: 10
95 | msgid "en"
96 | msgstr "en"
97 |
98 | # filename: pkg/i18n/i18n.go, offset: 3695, line: 155, column: 46
99 | msgid "date_formats.json"
100 | msgstr "date_formats.json"
101 |
102 | # filename: pkg/i18n/i18n.go, offset: 379, line: 27, column: 29
103 | msgid "locales"
104 | msgstr "locales"
105 |
106 | # filename: pkg/i18n/i18n.go, offset: 794, line: 47, column: 14
107 | msgid "languageCode:"
108 | msgstr "languageCode:"
109 |
110 | # filename: pkg/i18n/lang.go, offset: 314, line: 14, column: 20
111 |
112 |
113 | # filename: pkg/i18n/lang.go, offset: 640, line: 23, column: 11
114 | msgid "ltr"
115 | msgstr "ltr"
116 |
117 | # filename: pkg/i18n/lang.go, offset: 682, line: 24, column: 11
118 | msgid "rtl"
119 | msgstr "rtl"
120 |
121 | # filename: pkg/i18n/lang.go, offset: 212, line: 12, column: 20
122 | msgid "el_GR"
123 | msgstr "Greek"
124 |
125 | msgid "en_US"
126 | msgstr "English"
127 |
128 | msgid "ar_SA"
129 | msgstr "Arabic"
130 |
131 | # filename: main.go, offset: 2592, line: 99, column: 29
132 | msgid "Super Mario 64"
133 | msgstr "Super Mario 64"
134 |
135 | # filename: main.go, offset: 2761, line: 100, column: 93
136 | msgid "1:20:41"
137 | msgstr "1:20:41"
138 |
139 | # filename: main.go, offset: 3005, line: 111, column: 18
140 | msgid "/speedruns"
141 | msgstr "/speedruns"
142 |
143 | # filename: main.go, offset: 3108, line: 113, column: 18
144 | msgid "/speedrun.html"
145 | msgstr "/speedrun.html"
146 |
147 | # filename: main.go, offset: 3364, line: 120, column: 22
148 | msgid ":8080"
149 | msgstr ":8080"
150 |
151 | # filename: main.go, offset: 2578, line: 99, column: 15
152 | msgid "Alex"
153 | msgstr "Alex"
154 |
155 | # filename: main.go, offset: 2697, line: 100, column: 29
156 | msgid "The Legend of Zelda: Ocarina of Time"
157 | msgstr "The Legend of Zelda: Ocarina of Time"
158 |
159 | # filename: main.go, offset: 2893, line: 106, column: 14
160 | msgid "failed to initialize i18n: %v"
161 | msgstr "failed to initialize i18n: %v"
162 |
163 | # filename: main.go, offset: 3053, line: 112, column: 18
164 | msgid "/speedruns/add"
165 | msgstr "/speedruns/add"
166 |
167 | # filename: main.go, offset: 3212, line: 116, column: 43
168 | msgid "/static/"
169 | msgstr "/static/"
170 |
171 | # filename: main.go, offset: 3617, line: 129, column: 33
172 | msgid "application/json"
173 | msgstr "application/json"
174 |
175 | # filename: main.go, offset: 2747, line: 100, column: 79
176 | msgid "Any%"
177 | msgstr "Any%"
178 |
179 | # filename: main.go, offset: 2634, line: 99, column: 71
180 | msgid "16:58"
181 | msgstr "16:58"
182 |
183 | # filename: main.go, offset: 2683, line: 100, column: 15
184 | msgid "Theo"
185 | msgstr "Theo"
186 |
187 | # filename: main.go, offset: 3249, line: 116, column: 80
188 | msgid "./static"
189 | msgstr "./static"
190 |
191 | # filename: main.go, offset: 4226, line: 150, column: 26
192 | msgid "Speedrun submitted successfully!"
193 | msgstr "Speedrun submitted successfully!"
194 |
195 | # filename: main.go, offset: 2970, line: 110, column: 18
196 | msgid "/"
197 | msgstr "/"
198 |
199 | # filename: main.go, offset: 3302, line: 119, column: 21
200 | msgid "Server listening on port %d..."
201 | msgstr "Server listening on port %d..."
202 |
203 | # filename: main.go, offset: 3601, line: 129, column: 17
204 | msgid "Content-Type"
205 | msgstr "Content-Type"
206 |
207 | # filename: pkg/model/speedrun.go, offset: 235, line: 10, column: 24
208 | msgid "json:\"submitted_at\""
209 | msgstr "json:\"submitted_at\""
210 |
211 | # filename: pkg/model/speedrun.go, offset: 76, line: 6, column: 24
212 | msgid "json:\"player_name\""
213 | msgstr "json:\"player_name\""
214 |
215 | # filename: pkg/model/speedrun.go, offset: 120, line: 7, column: 24
216 | msgid "json:\"game\""
217 | msgstr "json:\"game\""
218 |
219 | # filename: pkg/model/speedrun.go, offset: 157, line: 8, column: 24
220 | msgid "json:\"category\""
221 | msgstr "json:\"category\""
222 |
223 | # filename: pkg/model/speedrun.go, offset: 198, line: 9, column: 24
224 | msgid "json:\"time\""
225 | msgstr "json:\"time\""
226 |
227 | msgid "EntryAdded"
228 | msgid_plural "EntriesAdded"
229 | msgstr[0] "%d new Entry Added today"
230 | msgstr[1] "%d new Entries today"
231 | msgstr[2] "No new Entries today"
--------------------------------------------------------------------------------