qor / i18n

I18n is a golang implementation, provides internationalization support for your application, with different backends support
MIT License
105 stars 25 forks source link

Reuse existing translation #12

Open labibramadhan opened 7 years ago

labibramadhan commented 7 years ago

Can i reuse existring translation? For example something like this:

en:
  greeting: Hello
  greetMySelf: {{.en.greeting}} me!
labibramadhan commented 7 years ago

I can resolve by writing this:

I18nTranslations := make(map[string]map[string]string)

translations := Globals.I18n.LoadTranslations()

for lang, locales := range translations {
  I18nTranslations[lang] = make(map[string]string)
  for localeKey, translation := range locales {
    I18nTranslations[lang][localeKey] = translation.Value
  }
}

output := Globals.I18n.T(
  "greeting",
  "en",
  I18nTranslations,
) 

but it doesn't resolve for cases that the reusable translations has parameters, for example:

en:
  greeting: Hello, good {{.Time}}
  greetMySelf: {{.en.greeting}} for me!

that should output: Hello, good evening for me! with arg: map[string]string{"Time": "evening"}

and it doesn't resolve for cases that the reusable translations is a nested variable, for example:

en:
  greeting:
    morning: Hello, good morning
    evening: Hello, good evening
  greetMySelf: {{.en.greeting.morning}} for me!
bodhi commented 7 years ago

@labibramadhan when using i18n on an app, it's a good idea to use full strings as messages. Building up messages from other small messages has 2 main disadvantages:

  1. It makes the logic for translations difficult to follow, and couples messages together
  2. It's often not flexible enough to allow translators to correctly translate the message. The context of the original message often affects the inflection of the parts of the message.

That said, your idea of nesting translations is an interesting one, but as you've discovered, translations don't compose very well. A translation is not just the original message, it's the message plus substitutions (variables).

So to compose two messages into a new one, you need the new message and possible variables from any sub-messages:

en:
  greeting: Hello, good {{.Time}} <= `greeting` id + `.Time` variable
  greetMySelf: {{.en.greeting}} for me! <= `greetMySelf` + (`greeting` id + variables from `.en.greeting`: `.Time`)

Since the messages are translated at run-time you need to localise the messages multiple times to "get to the bottom" of the translation nesting:

  1. Get en translation for greetMySelf.
  2. Discover that the translation contains en.greeting
  3. Get en translation for greeting <= recursion!
  4. Discover that greeting doesn't reference other translations
  5. ...

Also, since languages will reuse messages in different ways, you don't even really know which variables you will need, so you then either need to manually inspect the message nesting, or make all possible variables available.


If instead of reusing translations, you just want to include translated messages in other message, you can translate the message and provide it manually as a substitution:

en:
  greeting:
    morning: morning
    text: Hello, good {{.Time}}
  greetMySelf: {{.en.greeting}} for me!

time := t("greeting.morning")
g := t("greeting.text.", { "Time": time })
output := t("greetMySelf": { "greeting": g })

This is basically the same as my recursion example above, but you're doing it by hand instead of the library doing it for you.