-
Notifications
You must be signed in to change notification settings - Fork 160
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
Add internationalization support #738
Conversation
a813fb7
to
a753e4a
Compare
eb524a5
to
bb11a14
Compare
f74b039
to
29bb88b
Compare
e4e5906
to
fd166d8
Compare
Finished documenting the pull request and created supplementary pull request for placeholder locale and Crowdin config, this is now ready for review! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here are a couple of things for the beginning.
ee70c9e
to
3e97ccb
Compare
3e97ccb
to
087cc68
Compare
Rebased. All translations have been removed, language switching modal is not included (will be added via a separate PR). Previous state lives here. |
54ad996
to
21c341b
Compare
d1ca214
to
a88a830
Compare
805f3fa
to
53879e9
Compare
Update as per 27 May, 2023: Nah, that's just Yarn being annoying. Not sure how to solve this issue, will try something later. |
3f5ddd7
to
7269b4c
Compare
ccfb62c
to
eac3e2c
Compare
Rebased to use pnpm (Yarn workaround removed), fixed the Crowdin config, and updated the description of pull request! 🙌 |
eac3e2c
to
540e96d
Compare
Rebased, all the VIntl dependencies are updated and pin specific FormatJS packages versions due to formatjs/formatjs#4126. #CJSMustDie |
Conflicts: package.json pnpm-lock.yaml
Adds support for translations using VIntl module as a peer dependency. Peer dependencies are dependencies that have to be provided by the consumer, which means that `@vintl/vintl` has to be installed and set up in Theseus and any other consumers. Translations for Omorphia are done in Omorphia to prevent duplicate work, and the consumer has to import the locale files like they do for with the styles already. Those locale files have to be registered in VIntl plugin and processed using `@vintl/unplugin`. `@vintl/unplugin` is a Unplugin (which means it works with Rollup, Vite, and even Webpack) that takes in locale files generated by `@formatjs/cli` utility and transforms them into the proper ES modules. This allows consumers to get rid of the `intl-messageformat-parser`, which has a huge toll on the bundle size otherwise, at the cost of inability to parse custom messages. To give an example of how this can be approached, docs have been updated to support changing the language used in examples. Knossos is already doing all this automatically by using `@vintl/nuxt`. `@formatjs/cli` allows to define messages directly and the code and then automatically extract them into the locale file. Collocation makes it easier to maintain the messages and see them right in the code. Translations are done using Crowdin, so Crowdin needs to be connected to this repository, just like it was connected to Knossos [^1]. This commit also introduces a config that allows to do just that. [^1]: modrinth/knossos#738 (comment)
Adds support for translations using VIntl module as a peer dependency. Peer dependencies are dependencies that have to be provided by the consumer, which means that `@vintl/vintl` has to be installed and set up in Theseus and any other consumers. Translations for Omorphia are done in Omorphia to prevent duplicate work, and the consumer has to import the locale files like they do for with the styles already. Those locale files have to be registered in VIntl plugin and processed using `@vintl/unplugin`. `@vintl/unplugin` is a Unplugin (which means it works with Rollup, Vite, and even Webpack) that takes in locale files generated by `@formatjs/cli` utility and transforms them into the proper ES modules. This allows consumers to get rid of the `intl-messageformat-parser`, which has a huge toll on the bundle size otherwise, at the cost of inability to parse custom messages. To give an example of how this can be approached, docs have been updated to support changing the language used in examples. Knossos is already doing all this automatically by using `@vintl/nuxt`. `@formatjs/cli` allows to define messages directly and the code and then automatically extract them into the locale file. Collocation makes it easier to maintain the messages and see them right in the code. Translations are done using Crowdin, so Crowdin needs to be connected to this repository, just like it was connected to Knossos [^1]. This commit also introduces a config that allows to do just that. [^1]: modrinth/knossos#738 (comment)
Adds support for translations using VIntl module as a peer dependency. Peer dependencies are dependencies that have to be provided by the consumer, which means that `@vintl/vintl` has to be installed and set up in Theseus and any other consumers. Translations for Omorphia are done in Omorphia to prevent duplicate work, and the consumer has to import the locale files like they do for with the styles already. Those locale files have to be registered in VIntl plugin and processed using `@vintl/unplugin`. `@vintl/unplugin` is a Unplugin (which means it works with Rollup, Vite, and even Webpack) that takes in locale files generated by `@formatjs/cli` utility and transforms them into the proper ES modules. This allows consumers to get rid of the `intl-messageformat-parser`, which has a huge toll on the bundle size otherwise, at the cost of inability to parse custom messages. To give an example of how this can be approached, docs have been updated to support changing the language used in examples. Knossos is already doing all this automatically by using `@vintl/nuxt`. `@formatjs/cli` allows to define messages directly and the code and then automatically extract them into the locale file. Collocation makes it easier to maintain the messages and see them right in the code. Translations are done using Crowdin, so Crowdin needs to be connected to this repository, just like it was connected to Knossos [^1]. This commit also introduces a config that allows to do just that. [^1]: modrinth/knossos#738 (comment)
Adds support for translations using VIntl module as a peer dependency. Peer dependencies are dependencies that have to be provided by the consumer, which means that `@vintl/vintl` has to be installed and set up in Theseus and any other consumers. Translations for Omorphia are done in Omorphia to prevent duplicate work, and the consumer has to import the locale files like they do for with the styles already. Those locale files have to be registered in VIntl plugin and processed using `@vintl/unplugin`. `@vintl/unplugin` is a Unplugin (which means it works with Rollup, Vite, and even Webpack) that takes in locale files generated by `@formatjs/cli` utility and transforms them into the proper ES modules. This allows consumers to get rid of the `intl-messageformat-parser`, which has a huge toll on the bundle size otherwise, at the cost of inability to parse custom messages. To give an example of how this can be approached, docs have been updated to support changing the language used in examples. Knossos is already doing all this automatically by using `@vintl/nuxt`. `@formatjs/cli` allows to define messages directly and the code and then automatically extract them into the locale file. Collocation makes it easier to maintain the messages and see them right in the code. Translations are done using Crowdin, so Crowdin needs to be connected to this repository, just like it was connected to Knossos [^1]. This commit also introduces a config that allows to do just that. [^1]: modrinth/knossos#738 (comment)
Adds support for translations using VIntl module as a peer dependency. Peer dependencies are dependencies that have to be provided by the consumer, which means that `@vintl/vintl` has to be installed and set up in Theseus and any other consumers. Translations for Omorphia are done in Omorphia to prevent duplicate work, and the consumer has to import the locale files like they do for with the styles already. Those locale files have to be registered in VIntl plugin and processed using `@vintl/unplugin`. `@vintl/unplugin` is a Unplugin (which means it works with Rollup, Vite, and even Webpack) that takes in locale files generated by `@formatjs/cli` utility and transforms them into the proper ES modules. This allows consumers to get rid of the `intl-messageformat-parser`, which has a huge toll on the bundle size otherwise, at the cost of inability to parse custom messages. To give an example of how this can be approached, docs have been updated to support changing the language used in examples. Knossos is already doing all this automatically by using `@vintl/nuxt`. `@formatjs/cli` allows to define messages directly and the code and then automatically extract them into the locale file. Collocation makes it easier to maintain the messages and see them right in the code. Translations are done using Crowdin, so Crowdin needs to be connected to this repository, just like it was connected to Knossos [^1]. This commit also introduces a config that allows to do just that. [^1]: modrinth/knossos#738 (comment)
This PR adds basic internationalisation support using the VIntl module, developed by me, specifically for Modrinth, based on the implementation present in the previous iteration of this pull request.
For maintainers #
This PR adds
@vintl/nuxt
module, which adds integration for VIntl, a Vue plugin that integrates FormatJS Intl with additional features, such as locale loading, changing, events, formatted components, etc. I encourage you to read the linked documentations for more information on how to use the APIs at your disposal.There are also additional extensions for FormatJS Intl, such as
compact-number
(a hacky way to pluralise numbers in compact notation more properly) andhow-ago
(based on implementation of$ago
by @venashial in Omorphia).For translators #
As a translator myself I tried to make experience for us translators as good as I possible can. That included adopting FormatJS Intl instead of using something like nuxt-i18n with vue-i18n. That means we are getting the ICU MessageFormat that is widely used, instead of custom interpolation provided by aforementioned modules/plugins.
FormatJS have a good summary of ICU MessageFormat syntax available on their site. If you are not familiar with this syntax, I encourage you to read the summary. It's pretty simple syntax that gets the job done. At our hands are
{variables}
(aka arguments),{plurals, plural, one {# plural} other {# plurals}}}
(where#
will be replaced with the number, but see the note below),{selectors, select, are_cool {are cool} other {are so-so}}
, dates and other formats.There are also additional features like rich text formatting. This lets you format entire components in a safe and easy-to-understand way. It's kind of like HTML, but not really.
To use this feature, you just need to know that each formatted component has a start tag (which looks like
<my-tag>
) and an end tag (which looks like</my-tag>
). Everything between these tags will be formatted. You can also use variables and other tags inside the tags, as long as they're in the source string.You can disable specific tags by putting them in quotation marks, like this:
Use the '<details>' tag to hide optional content
.Crowdin should help you to reduce the noise by mangling the tag names into zero-indexed numbers. E.g.
<strong>Warning:</strong> you are now warned. <italic>Please be mindful of that</italic>.
will become<0>Warning:</0> you are now warned. <1>Please be mindful of that</1>.
.As we progress with localisation, I will try to add screenshots and even edit context for some strings that use variables, but otherwise variable names should represent well what values they hold, and translation keys tell where the translation is used, but don't fear to ask in Crowdin comments for additional context.
Merge and integration #
Step 1. Merge this pull request
This pull request currently does not have any major user facing changes, so it's safe to merge as is.
Step 2. Connect Crowdin
After the merge of this pull request, you should connect this repository to Crowdin through Integrations / GitHub settings (only project managers can see this page). To do so, open the linked page and:
If you're feeling adventurous, you can learn about Crowdin CLI and set up Crowdin GitHub Action instead, which will give you more control on how the translations are synced.
Explanations:
After connection, Crowdin will immediately start synchronisation. Our Crowdin bot on GitHub will create a branch for master, and pull request that adds translations, it will be constantly updated at configured interval and eventually needs to be merged. Delete bot's branch to force a rebase (deleting a branch closes active pull request).
Step 3. Adjust workflow.
After this feature is finally merged, some adjustments have to be made to workflow.
Translating features of mass interest. When you know that feature you are currently working on will be announced to public and might gain public interest, consider giving translators a chance to translate it during the beta testing by manually enabling feature branches on Crowdin or leaving feature on staging for some time and then syncing translations before merging to prod.
By doing this you are ensuring that users in other languages will have better experience and first impression from the new feature since it's already translated.
For example, monetisation was a feature of mass interest since it was anticipated and announced everywhere, so it'd made sense to translate it before release. On the other hand, Re-word "my profile" to "your profile"/"this profile" #766, which changed wording in menu from ‘my profile’ to ‘your profile’ could've been merged as is, with translations coming later to master.
(Not necessarily) Frequent
master
translation merges after new features are out. In addition to above, when any new features are released, it makes sense to merge translations more often (e.g. once in two days vs once a week), since untranslated feature might catch attention of speakers of languages where it was not translated and they might go to Crowdin and contribute the translations.Adding languages. As mentioned above, to add languages to the site you have to explicitly enable them in
nuxt.config.js
, otherwise translations will exist but won't be used. Remember to also configure Crowdin language mappings to use correct locale codes (widened BCP 47 tags). They are already configured for existing languages, but need to be configured for new ones.Also remember that some ISO language codes (required for SEO) don't match BCP 47 tags, for those locales
meta.static.iso
needs to be added to locale descriptor undervintl.locales[?]
ofnuxt.config.js
.Extract translations. Never edit
locales/en-US/index.json
manually. Instead, runpnpm intl:extract
, which will iterate over all of the files in the project and extract the descriptors defined usingdefineMessage
ordefineMessages
meta-functions.Additional information #
If you remember, this pull request previously included translations for all of the pages, as well as a language picker. These were specifically omitted to take a different approach on things: instead of shipping everything at once, pages will be made translatable one by one, and features like language switcher will be added in separate pull requests.
The reason for this approach is that housekeeping a huge amount of changes (which make pages translatable) is a lot of work, and very difficult. Doing pages one by one and keeping PRs like this small makes them much easier to review.
Rest assured, the work already done is not lost, every revision lives in its own branch of my fork, and can be used as a reference:
Other things to note:
Unfortunately, we are not going to support Right-to-Left languages at first, as we cannot guarantee proper support for these due to maintenance costs that we are not willing to take. We may enable RTL languages on staging version of the site for testing and allow contribution of fixes for them.
This will not change anything regarding the content on the site, which will still be provided mostly in English language. There's an open idea about adding translatable project descriptions, go upvote it! As for now, you can use details blocks as a workaround.
I know the frog page is is sacred and inviolable, but it's the least updated and visible page of the site, so it makes it a good initial playground to see that everything is working correctly. I hope the frog society can forgive me for this, as it's only temporary, and done in a very frog way.
Credits #
Some inspiration was taken from
nuxt-i18n
module andvue-i18n
plugin APIs. Their libraries provided guidance of how changes can be implemented in the early days. Also, big thanks to FormatJS authors and contributors for providing libraries that power this contribution.Support my work. This contribution took a lot of time and energy, so if you appreciate my efforts, please consider supporting me, this would really help out these days.