Description
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:
- keywords doesn't often make a lot of sense (had to check the source code to guess the context/meaning of some of them)
- contributing becomes "harder". I'm introducing a new feature with some sentences, I have to check : If these sentences exists as keywords somewhere. If not, where should I put them ? under what namespaces ?
- even reading the code source gets tedious: https://github.com/gitpoint/git-point/blob/master/src/auth/screens/events.screen.js#L491-L502
- concatenation is bad:
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 language - keeping translations up to date is tedious, as if someone changes the english value of the key, there's a big chance it will be missed by translators : c5b90da
- currently, adding a entry
en.js
break all other languages. Contributors are using Google Translate in order to avoid this: feat(starred-count): show starred count #271 (comment) - last 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 :
Coding your app
- 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"
});
-
Injected values can be strings, or ... React Components !
t("{user} made {repository} public", { user: "Houssein", repository: (<Button title="gitpoint/gitpoint" onPress={()=>console.log("you clicked")}>gitpoint/git-point</Button>) })
-
Pluralization handling :
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.
Actual translations
A string extraction script is provided and takes the following parameters :
- sourcePath: The directory to be scanned for strings
- translator: The function used in your app to trigger translations (
t
in the above examples) - languages: A comma separated list of languages code
- messagePath: The directory where the messages files will be generated
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()"
};
- Any string not translated ( = "" ) will fallback to english (or the native language used to develop the app)
- The script takes care of merging new extracted sentences with previously translated ones
- If a sentence is removed from the application, it will be surrounded with @ symbols to hint the translator.
An additional script could be developed to make sure that all translations are up-to-date :
- run the extraction process
- iterate on generated files to test : if there are missing translations (empty) or obsolete ones (@ some old string@)
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