๐๏ธ 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
intvalues will always have0, notnullorNoneas in JavaScript or Python. That's why if we don't specify theunreadparameter in a query, the template will be set it to0.
๋ค์์ผ๋ก ์ธ์ด๋ฅผ๐ช๐ธ ์คํ์ธ ์ฌ๋.ํ์ด์ง ํ๋จ์ ์๋ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ์ ์ ์ง์ ๋งค๊ฐ๋ณ์
http://localhost:3000/์ ์ถ๊ฐํฉ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋ค๋ฅธ ์ธ์ด๋ก ๋์ด๊ฐ๋ฉด๐ท๐บ ๋ฌ์์์ด:

๐ You can play around with the value of
unreadto 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๊ฐ์ ์์ฃผ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์ ํ์ต ์ฌ์ดํธ๋ฅผ ์ ๋ฆฌํ์ต๋๋ค