๐๏ธ Golang ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฝ๊ฒ ๋ฒ์ญํ ์ ์๋ ๋ฐฉ๋ฒ
์๊ฐํ๋ค.
ใใ็!โ๏ธ ๋ค์ค ์ธ์ด ์ฌ์ฉ์๋ฅผ ์ํด Go ์์ฉ ํ๋ก๊ทธ๋จ์ ์ค๋นํ๊ณ ์๊ฑฐ๋ REST API์์ ์๋ก ๋ค๋ฅธ ์ธ์ด์ ๋ํ ์ง์๋ง ํ์ํ๋ฉด ์ค์ํ ์ฃผ์ ๋ฅผ ํ ๋ก ํด ๋ณด๊ฒ ์ต๋๋ค.
์ด ํ์ ๋ ๋ณด๊ธฐ์ ๊ทธ๋ ๊ฒ ๊ฐ๋จํ์ง ์๋ค.๋ชจ๋ ์ธ์ด๋ ์ซ์๋ฅผ ์ฌ์ฉํ ๋ ๋จ์ด ํ์์ ์์ด ์์ ๋ง์ ํน์ํ ์์๋ฅผ ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ด๋ค.์๋ฅผ ๋ค์ด, ๋ฌ์์์ด์์๋ ํญ๋ชฉ ์์ ์ธ ๊ฐ์ง ๋ค๋ฅธ ๋ณํ์ด ์์ต๋๋ค.
one
, 1ํญ;few
, 2ํญ;many
, 3+ํญ;๐ค And this must be understood when translating the application!
๊ฑฑ์ ํ์ง ๋ง๋ผ, ๋ชจ๋ ๊ฒ์ด ๊ณง ์ ์๋ฐฐ๋ ๊ฒ์ด๋ค.
๐ ์นดํ๋ก๊ทธ
Prepare the project for translation
ํ๋ก์ ํธ์ ์์ค ์ฝ๋
์, ์ฝ๋๋ฅผ ๋จผ์ ๋ณด๋ ๊ฒ์ ์ข์ํ๋ ์ฌ๋๋ค์๊ฒGitHub์ ์ ์ฅ์๋ฅผ ๋ง๋ค์์ต๋๋ค.
์ฝ๋ค / ํํ ๋ฆฌ์ผ - go-i18n
๐ ์์ต์: Golang ์ดํ๋ฆฌ์ผ์ด์ ์ ์ฝ๊ฒ ๋ฒ์ญํ๋ ๋ฐฉ๋ฒ
โ Table of contents
๋ฒ์ญ ํญ๋ชฉ ์ค๋น
๋๋ ์ด๋ฏธ ์ด ์กฐ์์ ๊ดํ ์ํํธ์จ์ด ํจํค์ง(Go core์ ๋ด์ฅ๋ ์ํํธ์จ์ด ํจํค์ง ํฌํจ)๋ฅผ ๋ง์ด ๋ณด์์ง๋ง, nicksnyder/go-i18n์ ๋ด๊ฐ ํ๋ก์ ํธ์์ ์ ์ผํ๊ฒ ์ฆ๊ฒจ ์ฌ์ฉํ๋ ์ํํธ์จ์ด ํจํค์ง์ด๋ค.์ฐ๋ฆฌ๋ ์ด ํน์ ํ ํจํค์ง๋ฅผ ์ฌ์ฉํ์ฌ ํ๋ ์ ํ ์ด์ ํ๋ก๊ทธ๋จ์ ๋ง๋ค ๊ฒ์ ๋๋ค.
๐ Please write in the comments which package for i18n you use and why!
โ Table of contents
์ฌ์ดํธ ์์ฉ
๋ค, Fiber ์น ํ๋ ์์ํฌ๋ฅผ ์์ฉ ํ๋ก๊ทธ๋จ์ ํต์ฌ์ผ๋ก ์ผ์ ํ๋ฅญํ ํ ํ๋ฆฟ ์ง์ (๋งค๋๋ฌ์ด ๋ฆฌ์ ๊ธฐ๋ฅ) ์ ๊ฐ์ถ๊ณ ์์ผ๋ฉฐ, ์ฝ๋๋ฅผ ์์ฑํ๊ณ ์ฝ๊ธฐ ์ฝ์ต๋๋ค.
๐ฅ Please read comments in code!
// ./main.go
package main
import (
"log"
"strconv"
"github.com/BurntSushi/toml"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/template/html"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
)
func main() {
// Create a new i18n bundle with default language.
bundle := i18n.NewBundle(language.English)
// Register a toml unmarshal function for i18n bundle.
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
// Load translations from toml files for non-default languages.
bundle.MustLoadMessageFile("./lang/active.es.toml")
bundle.MustLoadMessageFile("./lang/active.ru.toml")
// Create a new engine by passing the template folder
// and template extension.
engine := html.New("./templates", ".html")
// Reload the templates on each render, good for development.
// Optional, default is false.
engine.Reload(true)
// After you created your engine, you can pass it
// to Fiber's Views Engine.
app := fiber.New(fiber.Config{
Views: engine,
})
// Register a new route.
app.Get("/", func(c *fiber.Ctx) error {
lang := c.Query("lang") // parse language from query
accept := c.Get("Accept-Language") // or, parse from Header
// Create a new localizer.
localizer := i18n.NewLocalizer(bundle, lang, accept)
// Set title message.
helloPerson := localizer.MustLocalize(&i18n.LocalizeConfig{
DefaultMessage: &i18n.Message{
ID: "HelloPerson", // set translation ID
Other: "Hello {{.Name}}", // set default translation
},
TemplateData: &fiber.Map{
"Name": "John",
},
})
// Parse and set unread count of emails.
unreadEmailCount, _ := strconv.ParseInt(c.Query("unread"), 10, 64)
// Config for translation of email count.
unreadEmailConfig := &i18n.LocalizeConfig{
DefaultMessage: &i18n.Message{
ID: "MyUnreadEmails",
One: "You have {{.PluralCount}} unread email.",
Other: "You have {{.PluralCount}} unread emails.",
},
PluralCount: unreadEmailCount,
}
// Set localizer for unread emails.
unreadEmails := localizer.MustLocalize(unreadEmailConfig)
// Return data as JSON.
if c.Query("format") == "json" {
return c.JSON(&fiber.Map{
"name": helloPerson,
"unread_emails": unreadEmails,
})
}
// Return rendered template.
return c.Render("index", fiber.Map{
"Title": helloPerson,
"UnreadEmails": unreadEmails,
})
})
// Start server on port 3000.
log.Fatal(app.Listen(":3000"))
}
โ Table of contentsํ์ํ ํ ํ๋ฆฟ
์ผ๋ฐ์ ์ผ๋ก ์ ๋ ๋ฏธ๋ฆฌ ์ ์๋ CSS ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ง ์์ง๋ง ์ด ํ๋ ์ ํ ์ด์ ์ ๊ฐ๋จํจ๊ณผ ์๋ฆ๋ค์์ ์ํด Bootstrap 5(
v5.0.0-beta3
) ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ํํ์ต๋๋ค.<!-- ./templates/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{.Title}}</title>
<link
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6"
crossorigin="anonymous"
/>
<style>
* {
font-family: sans-serif;
color: #333333;
}
</style>
</head>
<body>
<div class="col-lg-8 mx-auto p-3 py-md-5">
<h1>{{.Title}}</h1>
<br />
<div class="row g-5">
<div class="col-md-6">
<ul class="icon-list">
<li>{{.UnreadEmails}}</li>
</ul>
</div>
</div>
<footer class="pt-5 my-5 text-muted border-top">
Switch to ๐ฌ๐ง <a href="/">English</a>, ๐ช๐ธ
<a href="/?lang=es">Espaรฑol</a>, ๐ท๐บ <a href="/?lang=ru">ะ ัััะบะธะน</a>.
</footer>
</div>
</body>
</html>
โ Table of contents์์ ์ธ์ด ์ถ์ถ
goi18n
CLI ์ค์น:go get -u github.com/nicksnyder/go-i18n/v2/goi18n
i18n.Message
๊ตฌ์กฐ ํ
์คํธ๋ฅผ ํ๋์ ๋ฉ์์ง ํ์ผ๋ก ์ถ์ถํ์ฌ ๋ฒ์ญํฉ๋๋ค(๊ธฐ๋ณธ๊ฐ, active.en.toml
).goi18n extract
# ./active.en.toml
HelloPerson = "Hello {{.Name}}"
[MyUnreadEmails]
one = "You have {{.PluralCount}} unread email."
other = "You have {{.PluralCount}} unread emails."
translate.es.toml
๊ณผ translate.ru.toml
) ๋ฅผ ์ํ ๋น ๋ฉ์์ง ํ์ผ์ ๋ง๋ญ๋๋ค.touch translate.es.toml translate.ru.toml
# For Espaรฑol:
goi18n merge active.en.toml translate.es.toml
# For Russian:
goi18n merge active.en.toml translate.ru.toml
# ./translate.ru.toml
[HelloPerson]
hash = "sha1-5b49bfdad81fedaeefb224b0ffc2acc58b09cff5"
other = "ะัะธะฒะตั, {{.Name}}"
[MyUnreadEmails]
hash = "sha1-6a65d17f53981a3657db1897630e9cb069053ea8"
one = "ะฃ ะฒะฐั ะตััั {{.PluralCount}} ะฝะตะฟัะพัะธัะฐะฝะฝะพะต ะฟะธััะผะพ."
other = "ะฃ ะฒะฐั ะตััั {{.PluralCount}} ะฝะตะฟัะพัะธัะฐะฝะฝัั
ะฟะธัะตะผ."
few = "ะฃ ะฒะฐั ะตััั {{.PluralCount}} ะฝะตะฟัะพัะธัะฐะฝะฝัั
ะฟะธััะผะฐ." # <-- new row for "few" count
many = "ะฃ ะฒะฐั ะตััั {{.PluralCount}} ะฝะตะฟัะพัะธัะฐะฝะฝัั
ะฟะธัะตะผ." # <-- new row for "many" count
goi18n merge
๊ณผ active.es.toml
์ผ๋ก ์ด๋ฆ์ ๋ฐ๊พธ๊ณ active.ru.toml
ํด๋์ ๋ฃ์ต๋๋ค.์์ฉ ํ๋ก๊ทธ๋จ ์์ ๋ฐ ์ธ์ด ์ฌ์ฉ
์ฐ๋ฆฌ๋ ๋ง์นจ๋ด ์ฐ๋ฆฌ์ ์์ฉ ํ๋ก๊ทธ๋จ์ ์์ํ ์ค๋น๊ฐ ๋์๋ค.
go run main.go
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# โ Fiber v2.7.1 โ
# โ http://127.0.0.1:3000 โ
# โ (bound on host 0.0.0.0 and port 3000) โ
# โ โ
# โ Handlers ............. 2 Processes ........... 1 โ
# โ Prefork ....... Disabled PID ............. 64479 โ
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๊ทธ๋../lang
ํ์ด์ง๋ฅผ ์ฝ๋๋ค.๋ณด์๋ค์ํผ ๊ธฐ๋ณธ์ ์ผ๋ก ์น ์ฌ์ดํธ๋ ํญ์ ์์ ์ด๋ฆฝ๋๋ค.๐ฌ๐ง ์์ฉ ํ๋ก๊ทธ๋จ ์ค์ ์ ์ง์ ๋ ๋๋ก ์์ด์ ๋๋ค.
๐ก In Golang unset
int
values will always have0
, notnull
orNone
as in JavaScript or Python. That's why if we don't specify theunread
parameter in a query, the template will be set it to0
.
๋ค์์ผ๋ก ์ธ์ด๋ฅผ๐ช๐ธ ์คํ์ธ ์ฌ๋.ํ์ด์ง ํ๋จ์ ์๋ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ์ ์ ์ง์ ๋งค๊ฐ๋ณ์
http://localhost:3000/
์ ์ถ๊ฐํฉ๋๋ค.๊ทธ๋ฆฌ๊ณ ๋ค๋ฅธ ์ธ์ด๋ก ๋์ด๊ฐ๋ฉด๐ท๐บ ๋ฌ์์์ด:
๐ You can play around with the value of
unread
to see how the word form automatically changes after a numeral for these languages.
๋ํ JSON์ ์์ ์๋ฆฌ๋ฅผ ์ค๋ช ํ๊ธฐ ์ํด ๊ฒ์์
unread
ํ๋ผ๋ฏธํฐ๋ฅผ ์ถ๊ฐํ์ฌ Fiber ์น ํ๋ ์์ํฌ๊ฐ ์ด๋ป๊ฒ JSON ํ์์ผ๋ก ๋น์ ์๊ฒ ๊ฐ์ ๋ด์ฉ์ ์ ๊ณตํ ์ ์๋์ง ์์๋ณด์ญ์์ค.โ Table of contents
ํ๊ธฐ
์ค์ ์น ์์ฉ ํ๋ก๊ทธ๋จ์์ ๋ฒ์ญ์ ์ ๋ฉด์ ์ ๋ฌํ๊ธฐ ์ํด REST API ๋ฐฉ๋ฒ์ ๋ค๋ฅธ ๋ณํ์ ๋ง๋ค ์ ์์ต๋๋ค.๊ทธ๋ฌ๋ ๊ฐ์ฅ ์ค์ํ ๊ฒ์ ๊ตญ์ ํ๋ก์ ํธ๋ฅผ ํ๋ ค๋ฉด ๋จผ์ ์ด๋ค ๊ตญ๊ฐ์ ์ธ์ด์ ๊ตฌ์ฒด์ ์ธ ์ํฉ์ ๊ณ ๋ คํด์ผ ํ๋ค๋ ๊ฒ์ด๋ค.
๊ณจ๋์ด๊ฐ ๋ค๋ฅธ ๊ฑฐ ํด์ค๊ฒ!๐
โ Table of contents
์ฌ์ง ๋ฐ ๋์์
๋ถํ.
๋ง์ฝ ๋น์ ์ด ์ด ๋ธ๋ก๊ทธ์ ์ ์ฌํ ๊ธ์ ๋ ๋ง์ด ๋ฐํํ๊ณ ์ถ๋ค๋ฉด, ์๋์ ํ๋ก ์ ๋ฐํํ๊ณ ์ ๋ฅผ ๊ตฌ๋ ํด ์ฃผ์ญ์์ค.๊ฐ์ฌํฉ๋๋ค!๐
๋ฌผ๋ก , ๋น์ ์ LiberaPay์ ์ ํ๋ฅผ ๊ฑธ์ด ์ ๋ฅผ ์ง์งํ ์ ์์ต๋๋ค.๋ชจ๋ ๊ธฐ๋ถ๊ธ์ ์๋ก์ด ๊ธ์ ์ฐ๊ณ ์ง์ญ ์ฌํ๋ฅผ ์ํ ๋น์๋ฆฌ ๊ฐ๋ฐ ์ฌ์ ์ ์ฐ์ผ ๊ฒ์ด๋ค.
Reference
์ด ๋ฌธ์ ์ ๊ดํ์ฌ(๐๏ธ Golang ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฝ๊ฒ ๋ฒ์ญํ ์ ์๋ ๋ฐฉ๋ฒ), ์ฐ๋ฆฌ๋ ์ด๊ณณ์์ ๋ ๋ง์ ์๋ฃ๋ฅผ ๋ฐ๊ฒฌํ๊ณ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ๋ณด์๋ค https://dev.to/koddr/an-easy-way-to-translate-your-golang-application-5egeํ ์คํธ๋ฅผ ์์ ๋กญ๊ฒ ๊ณต์ ํ๊ฑฐ๋ ๋ณต์ฌํ ์ ์์ต๋๋ค.ํ์ง๋ง ์ด ๋ฌธ์์ URL์ ์ฐธ์กฐ URL๋ก ๋จ๊ฒจ ๋์ญ์์ค.
์ฐ์ํ ๊ฐ๋ฐ์ ์ฝํ ์ธ ๋ฐ๊ฒฌ์ ์ ๋ (Collection and Share based on the CC Protocol.)
์ข์ ์นํ์ด์ง ์ฆ๊ฒจ์ฐพ๊ธฐ
๊ฐ๋ฐ์ ์ฐ์ ์ฌ์ดํธ ์์ง
๊ฐ๋ฐ์๊ฐ ์์์ผ ํ ํ์ ์ฌ์ดํธ 100์ ์ถ์ฒ ์ฐ๋ฆฌ๋ ๋น์ ์ ์ํด 100๊ฐ์ ์์ฃผ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์ ํ์ต ์ฌ์ดํธ๋ฅผ ์ ๋ฆฌํ์ต๋๋ค