๐Ÿˆ‚๏ธ Golang ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‰ฝ๊ฒŒ ๋ฒˆ์—ญํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•

24130 ๋‹จ์–ด tutorialbeginnersgoshowdev

์†Œ๊ฐœํ•˜๋‹ค.


ใ‚ˆใ†็š†!โœŒ๏ธ ๋‹ค์ค‘ ์–ธ์–ด ์‚ฌ์šฉ์ž๋ฅผ ์œ„ํ•ด Go ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ์ค€๋น„ํ•˜๊ณ  ์žˆ๊ฑฐ๋‚˜ REST API์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ์–ธ์–ด์— ๋Œ€ํ•œ ์ง€์›๋งŒ ํ•„์š”ํ•˜๋ฉด ์ค‘์š”ํ•œ ์ฃผ์ œ๋ฅผ ํ† ๋ก ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
์ด ํ™”์ œ๋Š” ๋ณด๊ธฐ์— ๊ทธ๋ ‡๊ฒŒ ๊ฐ„๋‹จํ•˜์ง€ ์•Š๋‹ค.๋ชจ๋“  ์–ธ์–ด๋Š” ์ˆซ์ž๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋‹จ์–ด ํ˜•์‹์— ์žˆ์–ด ์ž์‹ ๋งŒ์˜ ํŠน์ˆ˜ํ•œ ์š”์†Œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.์˜ˆ๋ฅผ ๋“ค์–ด, ๋Ÿฌ์‹œ์•„์–ด์—์„œ๋Š” ํ•ญ๋ชฉ ์ˆ˜์— ์„ธ ๊ฐ€์ง€ ๋‹ค๋ฅธ ๋ณ€ํ˜•์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • one, 1ํ•ญ;
  • few, 2ํ•ญ;
  • many, 3+ํ•ญ;
  • ๐Ÿค” And this must be understood when translating the application!


    ๊ฑฑ์ •ํ•˜์ง€ ๋งˆ๋ผ, ๋ชจ๋“  ๊ฒƒ์ด ๊ณง ์ž˜ ์•ˆ๋ฐฐ๋  ๊ฒƒ์ด๋‹ค.

    ๐Ÿ“ ์นดํƒˆ๋กœ๊ทธ

  • Source code of the project

  • Prepare the project for translation
  • Website application
  • Template for display
  • Extracting the original language
  • Launch the application and playing with languages
  • Afterword
  • ํ”„๋กœ์ ํŠธ์˜ ์†Œ์Šค ์ฝ”๋“œ


    ์˜ˆ, ์ฝ”๋“œ๋ฅผ ๋จผ์ € ๋ณด๋Š” ๊ฒƒ์„ ์ข‹์•„ํ•˜๋Š” ์‚ฌ๋žŒ๋“ค์—๊ฒŒ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
    
  • ์€ Go ์†Œ์Šค ํŒŒ์ผ์˜ ๋ชจ๋“  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 ํด๋”์— ๋„ฃ์Šต๋‹ˆ๋‹ค.
  • ์ด๋ ‡๊ฒŒ!
  • โ†‘ Table of contents

    ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ ์‹œ์ž‘ ๋ฐ ์–ธ์–ด ์‚ฌ์šฉ


    ์šฐ๋ฆฌ๋Š” ๋งˆ์นจ๋‚ด ์šฐ๋ฆฌ์˜ ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ์‹œ์ž‘ํ•  ์ค€๋น„๊ฐ€ ๋˜์—ˆ๋‹ค.
    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 have 0, not null or None as in JavaScript or Python. That's why if we don't specify the unread parameter in a query, the template will be set it to 0.


    ๋‹ค์Œ์œผ๋กœ ์–ธ์–ด๋ฅผ๐Ÿ‡ช๐Ÿ‡ธ ์ŠคํŽ˜์ธ ์‚ฌ๋žŒ.ํŽ˜์ด์ง€ ํ•˜๋‹จ์— ์žˆ๋Š” ๋งํฌ๋ฅผ ํด๋ฆญํ•˜์—ฌ ์ •์ˆ˜ ์งˆ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ 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

    ์‚ฌ์ง„ ๋ฐ ๋™์˜์ƒ

  • ๋น…์‡ผ์Šคํƒ€ํฌ https://shostak.dev
  • ๋ถ€ํ•„.


    ๋งŒ์•ฝ ๋‹น์‹ ์ด ์ด ๋ธ”๋กœ๊ทธ์— ์œ ์‚ฌํ•œ ๊ธ€์„ ๋” ๋งŽ์ด ๋ฐœํ‘œํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์•„๋ž˜์— ํ‰๋ก ์„ ๋ฐœํ‘œํ•˜๊ณ  ์ €๋ฅผ ๊ตฌ๋…ํ•ด ์ฃผ์‹ญ์‹œ์˜ค.๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค!๐Ÿ˜˜
    ๋ฌผ๋ก , ๋‹น์‹ ์€ LiberaPay์— ์ „ํ™”๋ฅผ ๊ฑธ์–ด ์ €๋ฅผ ์ง€์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.๋ชจ๋“  ๊ธฐ๋ถ€๊ธˆ์€ ์ƒˆ๋กœ์šด ๊ธ€์„ ์“ฐ๊ณ  ์ง€์—ญ ์‚ฌํšŒ๋ฅผ ์œ„ํ•œ ๋น„์˜๋ฆฌ ๊ฐœ๋ฐœ ์‚ฌ์—…์— ์“ฐ์ผ ๊ฒƒ์ด๋‹ค.

    ์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ