From 00ce7e0f8ba67fbace6c31452361cf4ced57336e Mon Sep 17 00:00:00 2001 From: Jaime Date: Tue, 16 Jul 2024 16:01:09 +0200 Subject: [PATCH 1/7] docs(i18n): localization --- astro.config.mjs | 6 + .../internationalization/localization.mdx | 125 ++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 src/content/docs/internationalization/localization.mdx diff --git a/astro.config.mjs b/astro.config.mjs index a040050..f7e36b2 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -82,6 +82,12 @@ export default defineConfig({ directory: "theming", }, }, + { + label: "Internationalization", + autogenerate: { + directory: "internationalization", + }, + }, ], }), react(), diff --git a/src/content/docs/internationalization/localization.mdx b/src/content/docs/internationalization/localization.mdx new file mode 100644 index 0000000..6101927 --- /dev/null +++ b/src/content/docs/internationalization/localization.mdx @@ -0,0 +1,125 @@ +--- +title: 🌐 Localization +description: Recommended practices to localize software and make it accessible in multiple languages. +sidebar: + order: 0 +--- + +In the modern and global world, it is likely that your app will be used by people that speak another language. With internationalization, you will write your app in a way that allows you to easily change texts and layouts based on the user language. + +At Very Good Ventures, we recommend using internationalization even if you are not planning to support other languages in your first version. The overhead is small and the advantages in the long run are big, making your project scalable and setting it up for success. + +## Definitions + +Before we start with the recommendations, let's define some terminology: + +- Locale: Set of properties that define the user region, language and other user preferences like the currency, time or number formats.[^1] +- Localization: Process of adapting software for a specific language by translating text and adding region specific layouts and components.[^1] +- Internationalization: Process of designing software in a way that can be adapted (localized) to different languages without engineering changes.[^1] + +[^1]: Richard Ishida, W3C, Susan K. Miller, Boeing. [Localization vs Internationalization][i18n_l10n_locale_definitions] + +:::note +Internationalization is often referred as i18n and localization as l10n. Did you know that the 18 and 10 in both acronyms refer to the number of characters between the first and the last letters of each term? +::: + +## Frontend + +Our frontend framework of choice, Flutter, supports localization in an easy way, but all the concepts here can be extrapolated to any other UI framework. + +1. Start by setting up internationalization. In Flutter, you will have to install the `fluter_localizations` and `intl` packages. Also, enable the `generate` flag in the `flutter` section of the pubspec file: + +```yaml +flutter: + generate: true +``` + +2. Add a localization configuration file in the root directory of your project, called `l10n.yaml` with the following content: + +```yaml +arb-dir: lib/l10n/arb +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart +``` + +3. Create your template localization file inside `/lib/l10n/arb/` called `app_en.arb`. + +```json +{ + "helloWorld": "Hello World!", +} +``` + +4. Add other languages by creating new App Resource Bundle (.arb) files in the same folder. For example let's create the Spanish translation in `app_es.arb`. + +```json +{ + "helloWorld": "¡Hola Mundo!" +} +``` + +5. Generate the localization files to be used across the app by running `flutter gen-l10n`. + +6. Add the localizations delegates and supported locales to your app widget: + +```dart +import 'package:flutter_localizations/flutter_localizations.dart'; + +const MaterialApp( + title: 'Localizations Sample App', + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, +); +``` + +7. Finally, you can use your localized messages across the app. + +```dart +Text( + AppLocalizations.of(context).helloWorld, + style: Theme.of(context).textTheme.bodyMedium, +) +``` + +:::tip +If you find yourself repeating `AppLocalizations.of(context)` many times and find it cumbersome, you can create an extension to make it easier to access the localized strings: + +```dart +extension AppLocalizationsX on BuildContext { + AppLocalizations get l10n => AppLocalizations.of(this); +} +``` + +::: + +Check out the [Flutter documentation][flutter_i18n_docs] on this topic to have more details about the implementation. + +:::tip +You can save time configuring the localizations when creating a new project by using the Very Good CLI and running `very_good create flutter_app `. This template will create the english template file and all the configuration to start using it, as well as a readme section explaining how to add new strings. +::: + +### UI libraries + +It's common to create components in a different package that does not have access to the localized strings. The easiest solution to support localization is allow these components to receive the required strings as a parameter and pass them from the main app. + +## Backend + +Some applications don't require the backend to send any user-facing strings to the frontend. However, there are cases where this is needed, like a recipes app where you won't be storing all recipes in the device. To internationalize your app, you can follow a similar approach as we did for the frontend: + +- Create as many databases as languages you want to support, with the translated content. +- Get user locale by receiving it in the API call. +- Decide which string should be returned based on the user locale. + +### Error messages + +One kind of messages that will always be sent from the backend are error messages. When dealing with errors we can have multiple strategies: silently fail, retry, show a message... Here we will not discuss what approach might be better, however, every time a message is shown, it has to be localized. + +Our recommendation is that the API returns the appropriate [HTTP status codes][http_status_codes] and the frontend can map those codes to localization keys and custom messages. + +If the error is not coming from an API call and therefore we're not using HTTP status codes, we can use a similar approach by defining our error constants, like `user_not_found` or `connection_unavailable` to then map them to a localized string. + +--- + +[i18n_l10n_locale_definitions]: https://www.w3.org/International/questions/qa-i18n +[flutter_i18n_docs]: https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization +[http_status_codes]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status From 7a9a4b5b8c8f0c76062f2a2258e6d1099f5950dd Mon Sep 17 00:00:00 2001 From: Jaime Date: Tue, 16 Jul 2024 17:13:06 +0200 Subject: [PATCH 2/7] docs: improve l10n.yaml file info --- src/content/docs/internationalization/localization.mdx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/content/docs/internationalization/localization.mdx b/src/content/docs/internationalization/localization.mdx index 6101927..e2bb4c1 100644 --- a/src/content/docs/internationalization/localization.mdx +++ b/src/content/docs/internationalization/localization.mdx @@ -40,8 +40,16 @@ flutter: arb-dir: lib/l10n/arb template-arb-file: app_en.arb output-localization-file: app_localizations.dart +nullable-getter: false +preferred-supported-locales: [en] ``` +Make sure to update these values based on your needs. We recommend setting up the preferred locale manually to avoid Flutter selecting it from a list in alphabetical order. + +:::note +Check out the [documentation][l10n_file_documentation] regarding the configuration values supported by the `l10n.yaml` file to know more. +::: + 3. Create your template localization file inside `/lib/l10n/arb/` called `app_en.arb`. ```json @@ -121,5 +129,6 @@ If the error is not coming from an API call and therefore we're not using HTTP s --- [i18n_l10n_locale_definitions]: https://www.w3.org/International/questions/qa-i18n +[l10n_file_documentation]: https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization#configuring-the-l10n-yaml-file [flutter_i18n_docs]: https://docs.flutter.dev/ui/accessibility-and-internationalization/internationalization [http_status_codes]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status From 7c484c0780d35531f5975651066d35c6f6522472 Mon Sep 17 00:00:00 2001 From: Jaime Date: Tue, 16 Jul 2024 17:54:18 +0200 Subject: [PATCH 3/7] docs: sort sections in alphabetical order --- astro.config.mjs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/astro.config.mjs b/astro.config.mjs index f7e36b2..7ebc9a6 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -64,6 +64,12 @@ export default defineConfig({ directory: "development", }, }, + { + label: "Internationalization", + autogenerate: { + directory: "internationalization", + }, + }, { label: "State Management", autogenerate: { @@ -82,12 +88,6 @@ export default defineConfig({ directory: "theming", }, }, - { - label: "Internationalization", - autogenerate: { - directory: "internationalization", - }, - }, ], }), react(), From 334423dcf2df65236954dab49f9a574072b14bd2 Mon Sep 17 00:00:00 2001 From: Jaime Date: Wed, 17 Jul 2024 11:18:56 +0200 Subject: [PATCH 4/7] docs: improve writing --- src/content/docs/internationalization/localization.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/content/docs/internationalization/localization.mdx b/src/content/docs/internationalization/localization.mdx index e2bb4c1..b84e24b 100644 --- a/src/content/docs/internationalization/localization.mdx +++ b/src/content/docs/internationalization/localization.mdx @@ -103,12 +103,12 @@ extension AppLocalizationsX on BuildContext { Check out the [Flutter documentation][flutter_i18n_docs] on this topic to have more details about the implementation. :::tip -You can save time configuring the localizations when creating a new project by using the Very Good CLI and running `very_good create flutter_app `. This template will create the english template file and all the configuration to start using it, as well as a readme section explaining how to add new strings. +You can save time configuring the localizations when creating a new project by using the Very Good CLI and running `very_good create flutter_app `. This command will create the demo counter app with the English template file and all the internationalization configuration to start using it, as well as a readme section explaining how to add new strings. ::: ### UI libraries -It's common to create components in a different package that does not have access to the localized strings. The easiest solution to support localization is allow these components to receive the required strings as a parameter and pass them from the main app. +It's common to create components in a different package that do not have access to the localized strings. The easiest solution to support localization is to allow these components to receive the required strings as parameters, passing them from the main app. ## Backend @@ -120,7 +120,7 @@ Some applications don't require the backend to send any user-facing strings to t ### Error messages -One kind of messages that will always be sent from the backend are error messages. When dealing with errors we can have multiple strategies: silently fail, retry, show a message... Here we will not discuss what approach might be better, however, every time a message is shown, it has to be localized. +One kind of message that will always be sent from the backend is an error message. When dealing with errors we can have multiple strategies: silently fail, retry, show a message... Here we will not discuss what approach might be better, however, every time a message is shown, it has to be localized. Our recommendation is that the API returns the appropriate [HTTP status codes][http_status_codes] and the frontend can map those codes to localization keys and custom messages. From 2625ba815988e8efbf648a70fa33cdbf9218e210 Mon Sep 17 00:00:00 2001 From: Jaime Date: Wed, 17 Jul 2024 11:30:52 +0200 Subject: [PATCH 5/7] docs: clarify error messages example --- src/content/docs/internationalization/localization.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/internationalization/localization.mdx b/src/content/docs/internationalization/localization.mdx index b84e24b..b3a0732 100644 --- a/src/content/docs/internationalization/localization.mdx +++ b/src/content/docs/internationalization/localization.mdx @@ -124,7 +124,7 @@ One kind of message that will always be sent from the backend is an error messag Our recommendation is that the API returns the appropriate [HTTP status codes][http_status_codes] and the frontend can map those codes to localization keys and custom messages. -If the error is not coming from an API call and therefore we're not using HTTP status codes, we can use a similar approach by defining our error constants, like `user_not_found` or `connection_unavailable` to then map them to a localized string. +However, there are times where the HTTP status code does not give enough information and we want to be more specific to the user. In this cases we should return an error constant and map it to the localized string in the app. For example, if we have a shopping cart where we can use a promo code, when we validate it, the server could return a 400 (bad request) with a custom error code in the body: `invalid_code`, `expired_code`, `limit_reached`, `unqualified_item`, `already_used`. --- From 9592a8ea82920a244287ce62edaa1cb82054a1d6 Mon Sep 17 00:00:00 2001 From: Jaime <52668514+jsgalarraga@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:32:39 +0200 Subject: [PATCH 6/7] docs: fix english Co-authored-by: Mark Fairless --- src/content/docs/internationalization/localization.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/internationalization/localization.mdx b/src/content/docs/internationalization/localization.mdx index b3a0732..8815fed 100644 --- a/src/content/docs/internationalization/localization.mdx +++ b/src/content/docs/internationalization/localization.mdx @@ -124,7 +124,7 @@ One kind of message that will always be sent from the backend is an error messag Our recommendation is that the API returns the appropriate [HTTP status codes][http_status_codes] and the frontend can map those codes to localization keys and custom messages. -However, there are times where the HTTP status code does not give enough information and we want to be more specific to the user. In this cases we should return an error constant and map it to the localized string in the app. For example, if we have a shopping cart where we can use a promo code, when we validate it, the server could return a 400 (bad request) with a custom error code in the body: `invalid_code`, `expired_code`, `limit_reached`, `unqualified_item`, `already_used`. +However, there are times where the HTTP status code does not give enough information and we want to be more specific to the user. In these cases, we should return an error constant and map it to the localized string in the app. For example, if we have a shopping cart where we can use a promo code, when we validate it, the server could return a 400 (bad request) with a custom error code in the body: `invalid_code`, `expired_code`, `limit_reached`, `unqualified_item`, `already_used`. --- From 90d0258dd20c74a35477ea2bc9eb602528f28f9f Mon Sep 17 00:00:00 2001 From: Jaime <52668514+jsgalarraga@users.noreply.github.com> Date: Mon, 29 Jul 2024 16:51:07 +0200 Subject: [PATCH 7/7] Improve wording Co-authored-by: Joanna May --- .../internationalization/localization.mdx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/content/docs/internationalization/localization.mdx b/src/content/docs/internationalization/localization.mdx index 8815fed..d7d93f0 100644 --- a/src/content/docs/internationalization/localization.mdx +++ b/src/content/docs/internationalization/localization.mdx @@ -7,7 +7,7 @@ sidebar: In the modern and global world, it is likely that your app will be used by people that speak another language. With internationalization, you will write your app in a way that allows you to easily change texts and layouts based on the user language. -At Very Good Ventures, we recommend using internationalization even if you are not planning to support other languages in your first version. The overhead is small and the advantages in the long run are big, making your project scalable and setting it up for success. +Even if you are not planning to support other languages in your app's first version, **we highly recommend using internationalization**. The overhead is small and the advantages in the long run are big, making your project scalable and setting it up for success. ## Definitions @@ -20,12 +20,12 @@ Before we start with the recommendations, let's define some terminology: [^1]: Richard Ishida, W3C, Susan K. Miller, Boeing. [Localization vs Internationalization][i18n_l10n_locale_definitions] :::note -Internationalization is often referred as i18n and localization as l10n. Did you know that the 18 and 10 in both acronyms refer to the number of characters between the first and the last letters of each term? +Internationalization is often referred as i18n and localization as l10n since the 18 and 10 in both acronyms refer to the number of characters between the first and the last letters of each term. ::: ## Frontend -Our frontend framework of choice, Flutter, supports localization in an easy way, but all the concepts here can be extrapolated to any other UI framework. +We can use Flutter's built-in support for localization. 1. Start by setting up internationalization. In Flutter, you will have to install the `fluter_localizations` and `intl` packages. Also, enable the `generate` flag in the `flutter` section of the pubspec file: @@ -100,13 +100,13 @@ extension AppLocalizationsX on BuildContext { ::: -Check out the [Flutter documentation][flutter_i18n_docs] on this topic to have more details about the implementation. +Check out the [Flutter documentation][flutter_i18n_docs] on this topic to find more details about the implementation. :::tip -You can save time configuring the localizations when creating a new project by using the Very Good CLI and running `very_good create flutter_app `. This command will create the demo counter app with the English template file and all the internationalization configuration to start using it, as well as a readme section explaining how to add new strings. +You can save time configuring the localizations when creating a new project by using Very Good CLI and running `very_good create flutter_app `. This command will create the demo counter app with the English template file and all the internationalization configuration to start using it, as well as a readme section explaining how to add new strings. ::: -### UI libraries +### UI Libraries It's common to create components in a different package that do not have access to the localized strings. The easiest solution to support localization is to allow these components to receive the required strings as parameters, passing them from the main app. @@ -114,17 +114,17 @@ It's common to create components in a different package that do not have access Some applications don't require the backend to send any user-facing strings to the frontend. However, there are cases where this is needed, like a recipes app where you won't be storing all recipes in the device. To internationalize your app, you can follow a similar approach as we did for the frontend: -- Create as many databases as languages you want to support, with the translated content. -- Get user locale by receiving it in the API call. +- Create database entries with translated content for each language you want to support. +- Require client to transmit the user's locale with a backend request or when starting a session. - Decide which string should be returned based on the user locale. ### Error messages -One kind of message that will always be sent from the backend is an error message. When dealing with errors we can have multiple strategies: silently fail, retry, show a message... Here we will not discuss what approach might be better, however, every time a message is shown, it has to be localized. +We can leverage multiple error-handling strategies on the client-side: silently fail, retry, show a message, etc. Whenever an error message is received, however, it must be localized. -Our recommendation is that the API returns the appropriate [HTTP status codes][http_status_codes] and the frontend can map those codes to localization keys and custom messages. +We recommend that the backend return the appropriate [HTTP status codes][http_status_codes] so the frontend can map those codes to localization keys and custom messages. -However, there are times where the HTTP status code does not give enough information and we want to be more specific to the user. In these cases, we should return an error constant and map it to the localized string in the app. For example, if we have a shopping cart where we can use a promo code, when we validate it, the server could return a 400 (bad request) with a custom error code in the body: `invalid_code`, `expired_code`, `limit_reached`, `unqualified_item`, `already_used`. +However, there are times where the HTTP status code does not give enough information and we want to be more specific to the user. In these cases, we should return an error constant and map it to a localized string in the app. For example, if we have a shopping cart where we can use a promo code, the server could return a 400 (bad request) with a custom error code in the body if the promo code was invalid: `invalid_code`, `expired_code`, `limit_reached`, `unqualified_item`, `already_used`, etc. ---