├── .env ├── .gitignore ├── README.md ├── date_formats.json ├── go.mod ├── go.sum ├── locales ├── ar_SA │ └── LC_MESSAGES │ │ └── default.po ├── default.po ├── el_GR │ └── LC_MESSAGES │ │ └── default.po └── en_US │ └── LC_MESSAGES │ └── default.po ├── main.go ├── pkg ├── handlers │ └── handlers.go ├── i18n │ ├── helpers.go │ ├── i18n.go │ └── lang.go └── model │ └── speedrun.go ├── scripts ├── extract-strings.sh └── init-locales.sh └── static ├── index.html ├── speedrun.html └── stylesheet.css /.env: -------------------------------------------------------------------------------- 1 | LANGUAGE=el_GR -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | .idea 3 | .phrase.yml 4 | .env 5 | cache -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-internationalization - PhraseApp.com 2 | --- 3 | Simple example of Go i18n App using `golang.org/x/text` and `leonelquinteros/gotext` 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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" -------------------------------------------------------------------------------- /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] "Καμία καταχωρήση σήμερα" -------------------------------------------------------------------------------- /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" -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 "$@" -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 |{{T .PlayerName}} | 25 |{{T .Game}} | 26 |{{T .Category}} | 27 |{{T .Time}} | 28 |{{T .Date}} | 29 |
---|---|---|---|---|
{{.PlayerName}} | 33 |{{.Game}} | 34 |{{.Category}} | 35 |{{.Time}} | 36 |{{FormatLocalizedDate .SubmittedAt $.CurrentLanguage}} | 37 |
{{TN "EntryAdded" "EntriesAdded" 5}}
41 | 42 |