Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE REQUEST] Make i18n expandable #1597

Closed
Dexus opened this issue Aug 18, 2020 · 16 comments
Closed

[FEATURE REQUEST] Make i18n expandable #1597

Dexus opened this issue Aug 18, 2020 · 16 comments

Comments

@Dexus
Copy link

Dexus commented Aug 18, 2020

I come from the corner of GNU Gettext users. I would like to have something similar for the i18n implementation.

What requirements would I have:

  • Loader (local and binary)
  • Gettext functions, so that several domains and contexts can be used.
  • message interfaces
  • PO/MO support, JSON (i18n-next etc.), the current ini format

https://github.com/leonelquinteros/gotext (with serveral fixes: https://git.deineagentur.com/DeineAgenturUG/gotext) could serve as a basis for inspiration.

What do you think?

@kataras
Copy link
Owner

kataras commented Aug 18, 2020

Hello @Dexus , they are implemented already, u can load translations from binary, and various file formats, language codes are simplified automatically e.g. en to en-us, you can use variables and text templates. Can u please provide me something u want to do and u cant with the current implementation? So I can understand the idea of urs

Thanks

@Dexus
Copy link
Author

Dexus commented Sep 7, 2020

Hi @kataras
Sorry for the delay.

I miss the possibility to translate strings with number counts like you can do with gettext. like ngettext("string {$1} dog", "string {$1} dogs", 24) which will print string 24 dogs based on the plural variant of the language. Thats why a gettext implementation is the best and a standard in software development.

I use Gettext in all my projects a cross multiple languages, like Swift, Kotlin, Java, Javascript, PHP, C/C++ and Go. So It would reallly helpfull to load also the po/mo files from gettext within the iris project directly and not with a extra middleware that is already there with iris i18n support.

Also it would be cool, to have a function or a way to translate path for multilinguale projects, currently I run a loop over each path registration and add the language + seperator + pathName and add a own url function, that find the path for the current language and the pathName. It clould all be easy also for the alternative html tags for the different languages. But with the currently implementation it worth nothing to create multilinguale projects with the included i18n system.

Hope I was able to make my Idea a bit more clear.

Thanks, Dexus

@AlbinoGeek
Copy link

Well, right now it appears that I18N is basically a fmt.Sprintf statement, in that it currently uses all the same formatting directives. This gives you a lot more expandability and configurability when compared to positional arguments, but it does not allow you to easily re-use the existing arguments without passing them multiple times.

I would see benefit for both frankly.

@kataras
Copy link
Owner

kataras commented Sep 10, 2020

@AlbinoGeek @Dexus does something like that fills your needs or? (all those can be done before too but I pushed 3 commits to make ur life easier, so grab the master to run it)

/main.go

package main

import (
	"text/template"

	"github.com/kataras/iris/v12"
	// go get -u github.com/gertd/go-pluralize
	"github.com/gertd/go-pluralize"
)

/*
 Iris I18n supports text/template inside the translation values.
 Follow this example to learn how to use that feature.
*/

func main() {
	app := newApp()
	app.Listen(":8080")
}

func newApp() *iris.Application {
	app := iris.New()

	pluralize := pluralize.NewClient()

	// Set custom functions per locale!
	app.I18n.Loader.Funcs = func(current iris.Locale) template.FuncMap {
		return template.FuncMap{
			"plural": func(word string, count int) string {
				// Your own implementation or use a 3rd-party package
				// like we do here.
				//
				// Note that this is only for english,
				// but you can use the "current" locale
				// and make a map with dictionaries to
				// pluralize words based on the given language.
				return pluralize.Pluralize(word, count, true)
			},
		}
	}

	app.I18n.Load("./locales/*/*.yml", "en-US", "el-GR")

	app.Get("/", func(ctx iris.Context) {
		text := ctx.Tr("HiDogs", iris.Map{
			"count": 2,
		}) // prints "Hi 2 dogs".
		ctx.WriteString(text)
	})

	return app
}

./locales/en-US/welcome.yml

Dog: "dog"
HiDogs: Hi {{plural (tr "Dog") .count }}

./locales/el-GR/welcome.yml

Dog: "σκυλί"
HiDogs: Γειά {{plural (tr "Dog") .count }}

@AlbinoGeek
Copy link

I did not know that it was composable, this will save me so much re-typing if I can literally do {{tr "otherKey"}} in the middle of my translation strings.

@kataras
Copy link
Owner

kataras commented Sep 10, 2020

Yes with the latest commits you can do it!! Order of keys should not matter too.. And do more and more because I exposed the ability to have template functions per locale. Waiting for @Dexus confirmation too so we can move forward.

@AlbinoGeek
Copy link

Would the following be valid syntax under the new commits?

; 400 error
badRequestTitle=Bad Request
badRequestDescription=There was something about your browser or entries that we could not accept, and we cannot provide any additional details. {{tr "pleaseGoBack"}}

; 500 error
internalServerErrorTitle=Internal Server Error
internalServerErrorDescription=An issue happened on our side, and we cannot provide any additional details. We apologize for the inconvenience. {{tr "pleaseGoBack"}}

pleaseGoBack=Please go back and check your form entry or try again at a later date.

@kataras
Copy link
Owner

kataras commented Sep 10, 2020

If the key exist in the locale folder (e.g. en-US) then it should be, that's the idea. However I didn't test it with the ini format + INI sections (yet) but I don't think it will fail, give it a try

@AlbinoGeek
Copy link

(in my example above, the key existed in the same file) -- and I am not using INI sections (as I assumed those were not supported), just INI comments. Sections look like:

[section] ; assuming this is not supported
; however, if it was, you would access this key like: section.key
key=value

@kataras
Copy link
Owner

kataras commented Sep 10, 2020

(in my example above, the key existed in the same file) -- and I am not using INI sections (as I assumed those were not supported), just INI comments. Sections look like:

[section] ; assuming this is not supported
; however, if it was, you would access this key like: section.key
key=value

They are supported and you should be able to get them like you assumed with section.key, internal code for that:

iris/i18n/loader.go

Lines 330 to 348 in cf0338d

// Includes the ini.DefaultSection which has the root keys too.
// We don't have to iterate to each section to find the subsection,
// the Sections() returns all sections, sub-sections are separated by dot '.'
// and we match the dot with a section on the translate function, so we just save the values as they are,
// so we don't have to do section lookup on every translate call.
for _, section := range f.Sections() {
keyPrefix := ""
if name := section.Name(); name != ini.DefaultSection {
keyPrefix = name + "."
}
for _, key := range section.Keys() {
m[keyPrefix+key.Name()] = key.Value()
}
}
return nil
}

Let me add an example for that case too, if it doesn't work anymore then it's a bug.

@AlbinoGeek
Copy link

I did not expect that! As I did not see it in the examples, I assumed (incorrectly) that it just wasn't a feature.

Go you! [good job]

@kataras
Copy link
Owner

kataras commented Sep 10, 2020

Yeah... examples are missing some details that the framework provides, that's why I am (trying) to write the book :D Thanks for your nice words! Let me know if you have any issues with i18n.

@kataras
Copy link
Owner

kataras commented Sep 10, 2020

@AlbinoGeek

[forms]
member = member
register = Become a {{tr "forms.member" }}
registered = registered
registered_members = There are {{ concat (plural (tr "forms.member") .count) (tr "forms.registered") }}

app := iris.New()
pluralize := pluralize.NewClient()
// Set custom functions per locale!
app.I18n.Loader.Funcs = func(current iris.Locale) template.FuncMap {
return template.FuncMap{
"plural": func(word string, count int) string {
// Your own implementation or use a 3rd-party package
// like we do here.
//
// Note that this is only for english,
// but you can use the "current" locale
// and make a map with dictionaries to
// pluralize words based on the given language.
return pluralize.Pluralize(word, count, true)
},
"uppercase": func(word string) string {
return strings.ToUpper(word)
},
"concat": func(words ...string) string {
return strings.Join(words, " ")
},
}
}
app.I18n.Load("./locales/*/*", "en-US", "el-GR")

app.Get("/members", func(ctx iris.Context) {
text := ctx.Tr("forms.registered_members", iris.Map{
"count": 42,
}) // en-US: prints "There are 42 members registered".
ctx.WriteString(text)
})

@AlbinoGeek
Copy link

AlbinoGeek commented Sep 13, 2020

@kataras I was not able to reproduce the "nested translation" shown in the example above.

I tried in two ways, and got two different results:

[user.connections]
Title1 = {{tr nav.User}} Connections
Title2 = {{tr "nav.User"}} Connections
# result1
{{tr nav.User}} Connections
# result2
template: layouts/base.html:30:3: executing "layouts/base.html" at <yield>:
  error calling yield: template: user/connections.html:4:38: executing "user/connections.html" at <call .tr "user.connections.Title2">:
  error calling call: runtime error: index out of range [0] with length 0

The first-way Title1 led to the exact text being displayed, without the sub-translation resolved.

The second way Title2 led to a template rendering error being shown in the browser and console.

@AlbinoGeek
Copy link

@kataras I was not able to reproduce the "nested translation" shown in the example above.

I tried in two ways, and got two different results:

[user.connections]
Title1 = {{tr nav.User}} Connections
Title2 = {{tr "nav.User"}} Connections
# result1
{{tr nav.User}} Connections
# result2
template: layouts/base.html:30:3: executing "layouts/base.html" at <yield>:
  error calling yield: template: user/connections.html:4:38: executing "user/connections.html" at <call .tr "user.connections.Title2">:
  error calling call: runtime error: index out of range [0] with length 0

The first-way Title1 led to the exact text being displayed, without the sub-translation resolved.

The second way Title2 led to a template rendering error being shown in the browser and console.

This appears to be fixed!

@kataras
Copy link
Owner

kataras commented Sep 29, 2020

That's great, it's time to close this too then 💯

@kataras kataras closed this as completed Sep 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants