Dears,
As mentionned in a previous issue, and on gitter, I think that the current system used for handling translations is flawed.
Here are the major problems I've seen/I predict:
user + ' made ' + repository + 'public', is perfect for English, but it may not be suited for another language. Let's not speak about the mess it will be when we introduce a first RTL languageen.js break all other languages. Contributors are using Google Translate in order to avoid this: https://github.com/gitpoint/git-point/pull/271#issue-251850959last but not least, making sure that all translations are up to date before pushing next release to App stores is impossible for @housseindjirdeh and will require him to wait for all translators to respond.
In the past days, and with the help of @Antoine38660 (<3 buddy) I worked on a proof of concept for a better translation system, designed to solve all these problems and make contributors lifes easier.
Its concept is clearly inspired a lot by a PHP framework called Yii, which handles i18n in a clever way. [1]
The POC is available here: https://github.com/machour/rn-i18n-poc
(please keep in mind, I only did jQuery in the past and discovered const, import, yarn, let, export a week ago. Antoine did its best to enhance stuff, but I kept on breaking things.)
Here are the main features of this poorly coded idea :
The application is developed using english sentences, surrounded by a function, and using placeholders to inject values :
t("{user} made {repository} public", {
user: "Houssein",
repository: "gitpoint/git-point"
});
t("{user} made {repository} public", {
user: "Houssein",
repository: (<Button title="gitpoint/gitpoint" onPress={()=>console.log("you clicked")}>gitpoint/git-point</Button>)
})
t("There {n,plural,=0{are no cats} =1{is one cat} =*{are # cats}}!" {
n: 0
}) // There are no cats!
t("There {n,plural,=0{are no cats} =1{is one cat} =*{are # cats}}!", {
n: 1
}) // There is one cat!
t("There {n,plural,=0{are no cats} =1{is one cat} =*{are # cats}}!", {
n: 42
}) // There are 42 cats!
You can see more features by running the app from the repo.
A string extraction script is provided and takes the following parameters :
t in the above examples)The script produces a JS file with this structure (fr.js for example)
export default {
"I think that {user} made {repository} public": "Je crois que {user} a rendu {repository} public",
"This string is not translated and will automatically fallback to {language}": "",
"This string should not trigger placeholders processing": "Cette phrase ne doit pas invoquer le processing des remplacements",
"This string uses {nested}": "Cette phrase utilises des {nested}",
"nested translating!": "traductions imbriqu茅es :)",
"Oops, I forgot to pass my {placeholder}": "Oups, j'ai oubli茅 de passer mon {placeholder}",
"A simple sentence without placeholders": "Une phrase simple sans motifs de remplacements",
"Two {consecutive} {placeholders}": "Deux {placeholders} {consecutive}",
"consecutive": "consecutifs",
"placeholders": "motifs de remplacements",
"A sentence {0} using {1} numerical {0} placeholders": "Une phrase {0} utilisant {1} des motifs {0} num茅riques",
"A simple sentence with only one placeholder passed as a {0} without wrapping it in an array": "Une phrase simple avec un seul motif de remplacement pass茅 en tant que {0} sans avoir 脿 en faire un tableau",
"There {n,plural,=0{are no cats} =1{is one cat} =*{are # cats}}!": "Il y a {n,plural,=0{aucun chat} =1{un chat} =*{# chats}}!",
"Hello {name}. This is a component using t()": "Bonjour {name}. Ceci est un composant utilisant t()"
};
An additional script could be developed to make sure that all translations are up-to-date :
Having a running POC to show, and not so many skills, I decided to stop here, show you what I've been rambling about lately and have a first round of comments (or applauses 馃槅 ).
Could that be the next great way of handling translations in a React Native app ? (and I know, it could be made into a framework agnostic base, with several implementations).
Comments please.
[1] - If you want to see where I stoleborrowed the concept : http://www.yiiframework.com/doc-2.0/guide-tutorial-i18n.html
This is fascinating :O. I'm so surprised I haven't heard of Yii before!
Some thoughts:
{ t("A simple sentence without placeholders") } to { t(Super simple sentence without placeholders") }
I'm assuming translations will fail unless I update the key (which is the string here) in each of the translation files correct? I'm only thinking it may be slightly easier to make this mistake with long key names instead of short distinct ones but I really don't think that's a deal breaker.
Not sure how the script works. Why is there a script to generate the localization files if we can then modify it? I still don't understand where exactly you write the translations for fr.js if the script auto generates this file :think:
With pluralization, I'm assuming the idea is we pass in a component variable to make rendering different text easier correct? For example:
t("There {n,plural,=0{are no products} =1{is one product} =*{are # produts}}!", {
n: products.length
})}
I think that is just amazing :heart_eyes:
If a sentence is removed from the application, it will be surrounded with @ symbols to hint the translator.
WOWOWOWOW THIS IS A GAME CHANGER. Do we need to run the script in order to have this show??
Injected values can be strings, or ... React Components !
That is awesome, but just thinking out loud here :thinking: Isn't there an issue with having components rendering between text when it comes to styling? I guess that's up to the user correct?
Some closing thoughts:
This looks powerful. Although I'm still trying to wrap my head around how all of it works, I can already see features that may blow other super simple key:value translation solutions out of the water. I think this can definitely be used for more than React Native and I'm happy to have GitPoint be a pilot ground for this library. @machour @Antoine38660 : you guys are wizards!
So thrilled you liked the idea 馃檶 馃檶 馃檶
In fact, I consider this a good feature, as the update you made changes the meaning of the sentence, and translations have to be updated accordingly.
I still don't understand where exactly you write the translations for fr.js if the script auto generates this file
I put them in that exact same file, as the extraction process won't override them ;)
Here's how the script works :
The merge rules are as follow :
Let's take your change in the 1st point as an example :
Originally we'd have in fr.js:
"A simple sentence without placeholders": "Une simple phrase sans motifs de remplacement",
One you've updated the source code with your change, and re-run the script, we'd have :
"Super simple sentence without placeholders": "",
"A simple sentence without placeholders": "@Une simple phrase sans motifs de remplacement@",
As a translator, keeping the obsolete sentence helps me to copy paste parts of it in the new sentence, before deleting it manually:
"Super simple sentence without placeholders": "Phrase super simple sans motifs de remplacement",
Yii also supports the 'select' keyword, which would give us something like this:
t('{name} is a {gender} and {gender,select,female{she} male{he} other{it}} loves Yii!', {
'name' : 'Snoopy',
'gender': 'dog',
}); // Snoopy is a dog and it loves Yii!
there is a lot more syntax sugar (numerical placeholders, single placeholder passed as a string, ..), all meant to simplify the i18n task to the max.
Now you can easily imagine a pre-release yarn task that would run the extraction script on all languages, then the generated/merged files for empty or obsolete translations. 馃槏
For now the translated sentences are always wrapped in <Text> by default, and in a <View> if a component is detected in a placeholder. There are cases where we don't want any wrapping at all. Others where we prefer something else than View as a wrapper.
Maybe a third optional parameter could do that, or maybe this needs a lot of more thinking, especially if we're going framework agnostic.
- if a new sentence is found in the source code: add it with an empty translation
- if a sentence is found in the source code and was already translated: keep its translation
- if a sentence is found in the translation file, but not in the source code: this sentence probably existed in the source code at some point, then was removed. There is no point in keeping it in the translation file, so we signal it to translators by surrounding it by @ marks.
Oh my god. _This is a game changer_. I can't even explain how much of an improvement this can be over the majority of existing translation libraries. I can almost guarantee that the number of people who would flock to use a library like this would be insane. Framework agnostic (in my opinion) would be something that would make this probably the best JavaScript translation library out there.
Now you can easily imagine a pre-release yarn task that would run the extraction script on all languages, then the generated/merged files for empty or obsolete translations.
馃槏 馃槏 馃槏 Throwing ideas out here, but what if you could output consoles to the terminal (and/or CI) so when this step is run as part of the build process: all the empty and obsolete translations are shown to the user 馃槏 馃槏 馃槏 .
For now the translated sentences are always wrapped in
by default, and in a if a component is detected in a placeholder. There are cases where we don't want any wrapping at all. Others where we prefer something else than View as a wrapper. Maybe a third optional parameter could do that, or maybe this needs a lot of more thinking, especially if we're going framework agnostic.
This will definitely need some more thinking in my opinion _but_ I don't think this is critical at all. Happy to have this experimentally with our app since it might work in some contexts such as @Antoine38660's.
Okay so I'm excited, and I'm sold. How soon are you envisioning releasing your library? More than willing to have this included into GitPoint as it's first app, after you cleared up some points I can already see how much this can help!
@RolfKoenders @andrewda @alejandronanez @lex111 etc... don't hesitate to drop some tips/suggestions/concerns if you have any at all as well!
Oh my gosh this is awesome. I'd love to contribute!!
Damn this is sweeet. Will check tomorrow morning 馃槏
Echoing @andrewda, will be happy to contribute so let us know if you need help with anything! @machour
I'm happy to know that you think the idea is cool and can be used for Gitpoint and other projects!
Our major problem was (is) to separate each part of our code / repository to create a lib "distributable"
馃槉 馃槉 馃槉 , thank you all for the positive feedback !
As for the release date, I don't have any in mind, but I think that we could come really fast with a first solid beta.
@Antoine38660 and I seems to lack the technical background in JS on how to implement this in the best manner, so I can't really come with a first version for us to work on. Thus I'd really need your help to be set on the right track and do a collective kick-ass i18n library.
I just compiled this ticket into a new document on my poc repository :
https://github.com/machour/rn-i18n-poc/blob/discussion/Discussion.md
This will allow us to discuss the development furthermore by adding comments on this PR and keep the conversation organized :
https://github.com/machour/rn-i18n-poc/pull/1/files
Closing this issue and moving there to add some comments ;)
@machour Just for reference, did you check this repo already? Maybe you can do something like them.
https://github.com/i18next/react-i18next
@alejandronanez oh, nice catch! i18next seems to be a great library and we sure may inspire stuff from them.
Ooooh, as usual, I missed an interesting discussion :disappointed: . So many details, I'll just write that I like the idea! :+1: I propose to consider the existing solutions:
https://github.com/yahoo/react-intl
https://github.com/joshswan/react-native-globalize
Will they help us?
I only read halve of the discussion but already wanted to share that this is amazing!
Finally had the time to clean this up and craft a package: https://github.com/s-i18n/s-i18n-react
A demo app is available to quick test it. Please feel free to open tickets / propose PRs !
Looking forward to have it powering GitPoint <3
Most helpful comment
So thrilled you liked the idea 馃檶 馃檶 馃檶
In fact, I consider this a good feature, as the update you made changes the meaning of the sentence, and translations have to be updated accordingly.
I put them in that exact same file, as the extraction process won't override them ;)
Here's how the script works :
The merge rules are as follow :
Let's take your change in the 1st point as an example :
Originally we'd have in
fr.js:One you've updated the source code with your change, and re-run the script, we'd have :
As a translator, keeping the obsolete sentence helps me to copy paste parts of it in the new sentence, before deleting it manually:
Yii also supports the 'select' keyword, which would give us something like this:
there is a lot more syntax sugar (numerical placeholders, single placeholder passed as a string, ..), all meant to simplify the i18n task to the max.
Now you can easily imagine a pre-release yarn task that would run the extraction script on all languages, then the generated/merged files for empty or obsolete translations. 馃槏
For now the translated sentences are always wrapped in
<Text>by default, and in a<View>if a component is detected in a placeholder. There are cases where we don't want any wrapping at all. Others where we prefer something else thanViewas a wrapper.Maybe a third optional parameter could do that, or maybe this needs a lot of more thinking, especially if we're going framework agnostic.