From a7e825ecdb9f244235d8cb4681f143dfe42142c2 Mon Sep 17 00:00:00 2001 From: Christoph Schmatzler Date: Sun, 3 Sep 2023 22:39:11 +0200 Subject: [PATCH] docs: move moduledoc to README --- README.md | 239 ++++++++++++++++++++++++++++++++++++++++++---- lib/idiom.ex | 260 +-------------------------------------------------- 2 files changed, 226 insertions(+), 273 deletions(-) diff --git a/README.md b/README.md index d0f6379..9b0a5df 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,13 @@ # Idiom -A new take on internationalisation in Elixir. - [![Hex.pm](https://img.shields.io/hexpm/v/idiom.svg)](https://hex.pm/packages/idiom) [![Documentation](https://img.shields.io/badge/documentation-gray)](https://hexdocs.pm/idiom/) -Please see the documentation on [HexDocs](https://hexdocs.pm/idiom/) for a full rundown on Idiom's features. - -## State of Idiom + -Idiom is in active development and should be considered pre-production software. Its core functionality works and is relatively well-tested, but some cases -(such as RTL languages) are not yet covered by tests. If you have knowledge about languages and scripts that might be considered edge cases, please get in -touch or just submit a pull request with test cases - I'll be forever grateful. -There are also no backends packaged with Idiom yet. I will soon start actively adding some, with Phrase Strings first, and then widening the library. - -That said, I *think* the API should be relatively stable at this point - I will still leave it at `0.x` for now, though, and not make any promises in that -regard. As such, I would not recommend using Idiom in any mission-critical software at this point. +A new take on internationalisation in Elixir. ## Basic usage -Interaction with Idiom happens through `t/3`. - ```elixir # Set the locale Idiom.put_locale("en-US") @@ -33,6 +21,7 @@ t("Hello Idiom!") t("Good morning, {{name}}. We hope you are having a great day.", %{name: "Tim"}) # With plural and interpolation +# `count` is a magic option that automatically is available as binding. t("You need to buy {{count}} carrots", count: 1) # With namespace @@ -43,11 +32,227 @@ t("Create your account") # With explicit locale t("Create your account", to: "fr") +# With fallback key +t(["Create your account", "Register"]) + # With fallback locale t("Create your account", to: "fr", fallback: "en") ``` -## Usage with Phoenix +## Installation + +To start off, add `idiom` to the list of your dependencies: +```elixir +def deps do + {:idiom, "~> 0.1"}, +end +``` + +Additionally, in order to be able refresh translations in the background, add Idiom's `Supervisor` to your application: +```elixir +def start(_type, _args) do + children = [ + Idiom, + ] + + # ... +end +``` + +## Configuration + +There are a few things around Idiom that you can configure on an application level. The following fence shows all of Idiom's settings and their defaults. + +```elixir +config :idiom, + default_locale: "en", + default_fallback: "en", + default_namespace: "default", + data_dir: "priv/idiom", + backend: nil +``` + +In order to configure your backend, please have a look at its module documentation. + +## Locales + +When calling `t/3`, Idiom looks at the following settings to determine which locale to translate the key to, in order of priority: + +1. The explicit `to` option. When you call `t("key", to: "fr")`, Idiom will always use `fr` as a locale. +2. The locale set in the current process. You can call `Idiom.put_locale/1` to set it. +Since this is just a wrapper around the process dictionary, it needs to be set for each process you are using Idiom in. +3. The `default_locale` setting. See the [Configuration](#module-configuration) section for more details on how to set it. + +### Resolution hierarchy + +> #### A note on examples {: .info} +> +> For ease of presentation, whenever an example in this module documentation includes a translation file for context, it will be merged from the multiple +> files that `Idiom.Source.Local` actually expects. Instead of giving you the contents of all `en/default.json`, `en-US/default.json`, `en-GB/default.json` +> and others, it will be represented here as one merged file, such as: +> ```json +> { +> "en": {"default": { [Contents of what would usually be `en/default.json` ] }}, +> "en-US": {"default": { [Contents of what would usually be `en-US/default.json` ] }}, +> ... +> } +> ``` + +Locale codes can consist of multiple parts. Taking `zh-Hant-HK` as an example, we have the language (`zh` - Chinese), the script (`Hant`, Tradtional) and the +region (`HK` - Hong Kong). For different regions, there might only be differences for some specific keys, whereas all other keys share a translation. In +order to prevent needless repetition in your translation workflow, Idiom will always try to resolve translations in all of language, language and script, and +language, script and region variants, in order of specifity. + +Taking the following file as an example (see also [File format](#module-file-format)): +```json +{ + "en": { + "default": { + "Create your account": "Create your account" + } + }, + "en-US": { + "default": { + "Take the elevator": "Take the elevator" + } + }, + "en-GB": { + "default": { + "Take the elevator": "Take the lift" + } + } +} +``` +The `Create your account` message is the same for both American and British English, whereas the key `Take the elevator` has different wording for each. +With Idiom's resolution hierarchy, you can use both `en-US` and `en-GB` to refer to the `Create your account` key as well. + +```elixir +t("Take the elevator", to: "en-US") +# -> Take the elevator +t("Take the elevator", to: "en-GB") +# -> Take the lift +# Will first try to resolve the key in the `en-US` locale, then, since it does not exist, try `en`. +t("Create your account", to: "en-US") +# -> Create your account +t("Create your account", to: "en-GB") +# -> Create your account +``` + +### Fallback keys + +For scenarios where multiple keys might apply, `t/3` allows specifying a list of keys as well. + +```elixir +t(["Create your account", "Register"], to: "en-US") +``` + +This snippet will first try to resolve the `Create your account` key, and fall back to resolving `Register` when it does not exist. + +### Fallback locales + +For when a key might not be available in the set locale, you can set a fallback locale. +A fallback can be either a string or a list of strings. If you set the fallback as a list, Idiom will return the translation of the first locale for which +the key is available. + +When you don't explicitly set a `fallback` for `t/3`, Idiom will try the `default_fallback` (see [Configuration](#module-configuration)). +When a key is available in neither the target **or** any of the fallback language, the key will be returned as-is. + + +```elixir +# will return the translation for `en` +t("Key that is only available in `en` and `fr`", to: "es", fallback: "en") +# will return the translation for `fr` +t("Key that is only available in `en` and `fr`", to: "es", fallback: ["fr", "en"]) +# will return the translation for `en`, which is set as `default_fallback` +t("Key that is only available in `en` and `fr`", to: "es") +# will return "Key that is not available in any locale" +t("Key that is not available in any locale", to: "es") +``` + +### Using fallback keys and locales together + +When both fallback keys and locales are provided, Idiom will first try to resolve all keys in each locale before jumping to the next one. +For example, the resolution order for `t(["Create your account", "Register"], to: "es", fallback: ["fr", "de"])` will be: + +1. `Create your account` in `es` +2. `Register` in `es` +3. `Create your account` in `fr` +4. `Register` in `fr` +5. `Create your account` in `de` +6. `Register` in `de` + +## Namespaces + +Idiom allows grouping your keys into namespaces. + +When calling `t/3`, Idiom looks at the following settings to determine which namespace to resolve the key in, in order of priority: +1. The `namespace` option, like `t("Create your account", namespace: "signup")` +2. The namespace set in the current process. You can call `Idiom.put_namespace/1` to set it. +Since this is just a wrapper around the process dictionary, it needs to be set for each process you are using Idiom in. +3. The `default_namespace` setting. See the [Configuration](#module-configuration) section for more details on how to set it. + +## Interpolation + +Idiom supports interpolation in messages. +Interpolation can be added by adding an interpolation key to the message, enclosing it in `{{}}`. Then, you can bind the key to any string by passing it as +key inside the second parameter of `t/3`. + +Taking the following file as an example (see also [File format](#module-file-format)): +```json +{ + "en": { + "default": { + "Welcome, {{name}}": "Welcome, {{name}}", + "It is currently {{temperature}} degrees in {{city}}": "It is currently {{temperature}} degrees in {{city}}" + } + } +} +``` + +These messages can then be interpolated as such: + +```elixir +t("Welcome, {{name}}", %{name: "Tim"}) +# -> Welcome, Tim +t("It is currently {{temperature}} degrees in {{city}}", %{temperature: "31", city: "Hong Kong"}) +# -> It is currently 31 degrees in Hong Kong +``` + +## Pluralisation + +Idiom supports the following key suffixes for pluralisation: + +- `zero` +- `one` +- `two` +- `few` +- `many` +- `other` + +Your keys, for English, might then look like this: + +```json +{ + "carrot_one": "{{count}} carrot" + "carrot_other": "{{count}} carrots" +} +``` + +You can then pluralise your messages by passing `count` to `t/3`, such as: + +```elixir +t("carrot", count: 1) +# -> 1 carrot +t("carrot", count: 2) +# -> 2 carrot +``` + +> #### `{{count}}` and pluralisation {: .info} +> +> As you can see in the above example, we are not passing an extra `%{count: x}` binding. This is because the `count` option acts as a magic binding that is +> automatically available for interpolation. + +## Backends -At the time of writing, Idiom doesn't (yet) have a deep integration with Phoenix. This might change in the future. To see a basic example on how to integrate -the two, check out [this repository](https://github.com/cschmatzler/idiom-phoenix-example). +Idiom is designed to be extensible with multiple over the air providers. Please see the modules in `Idiom.Backend` for the ones built-in, and always feel +free to extend the ecosystem by creating new ones. diff --git a/lib/idiom.ex b/lib/idiom.ex index 5687694..de85670 100644 --- a/lib/idiom.ex +++ b/lib/idiom.ex @@ -1,260 +1,8 @@ defmodule Idiom do - @moduledoc """ - A new take on internationalisation in Elixir. - - ## Basic usage - - ```elixir - # Set the locale - Idiom.put_locale("en-US") - - t("landing.welcome") - - # With natural language key - t("Hello Idiom!") - - # With interpolation - t("Good morning, {{name}}. We hope you are having a great day.", %{name: "Tim"}) - - # With plural and interpolation - # `count` is a magic option that automatically is available as binding. - t("You need to buy {{count}} carrots", count: 1) - - # With namespace - t("Create your account", namespace: "signup") - Idiom.put_namespace("signup") - t("Create your account") - - # With explicit locale - t("Create your account", to: "fr") - - # With fallback key - t(["Create your account", "Register"]) - - # With fallback locale - t("Create your account", to: "fr", fallback: "en") - ``` - - Also check out the [Cheatsheet](cheatsheet.html)! - - ## Installation - - To start off, add `idiom` to the list of your dependencies: - ```elixir - def deps do - {:idiom, "~> 0.1"}, - end - ``` - - Additionally, in order to be able refresh translations in the background, add Idiom's `Supervisor` to your application: - ```elixir - def start(_type, _args) do - children = [ - Idiom, - ] - - # ... - end - ``` - - ## Configuration - - There are a few things around Idiom that you can configure on an application level. The following fence shows all of Idiom's settings and their defaults. - - ```elixir - config :idiom, - default_locale: "en", - default_fallback: "en", - default_namespace: "default", - data_dir: "priv/idiom", - backend: nil - ``` - - In order to configure your backend, please have a look at its module documentation. - - ## Locales - - When calling `t/3`, Idiom looks at the following settings to determine which locale to translate the key to, in order of priority: - - 1. The explicit `to` option. When you call `t("key", to: "fr")`, Idiom will always use `fr` as a locale. - 2. The locale set in the current process. You can call `Idiom.put_locale/1` to set it. - Since this is just a wrapper around the process dictionary, it needs to be set for each process you are using Idiom in. - 3. The `default_locale` setting. See the [Configuration](#module-configuration) section for more details on how to set it. - - ### Resolution hierarchy - - > #### A note on examples {: .info} - > - > For ease of presentation, whenever an example in this module documentation includes a translation file for context, it will be merged from the multiple - > files that `Idiom.Source.Local` actually expects. Instead of giving you the contents of all `en/default.json`, `en-US/default.json`, `en-GB/default.json` - > and others, it will be represented here as one merged file, such as: - > ```json - > { - > "en": {"default": { [Contents of what would usually be `en/default.json` ] }}, - > "en-US": {"default": { [Contents of what would usually be `en-US/default.json` ] }}, - > ... - > } - > ``` - - Locale codes can consist of multiple parts. Taking `zh-Hant-HK` as an example, we have the language (`zh` - Chinese), the script (`Hant`, Tradtional) and the - region (`HK` - Hong Kong). For different regions, there might only be differences for some specific keys, whereas all other keys share a translation. In - order to prevent needless repetition in your translation workflow, Idiom will always try to resolve translations in all of language, language and script, and - language, script and region variants, in order of specifity. - - Taking the following file as an example (see also [File format](#module-file-format)): - ```json - { - "en": { - "default": { - "Create your account": "Create your account" - } - }, - "en-US": { - "default": { - "Take the elevator": "Take the elevator" - } - }, - "en-GB": { - "default": { - "Take the elevator": "Take the lift" - } - } - } - ``` - The `Create your account` message is the same for both American and British English, whereas the key `Take the elevator` has different wording for each. - With Idiom's resolution hierarchy, you can use both `en-US` and `en-GB` to refer to the `Create your account` key as well. - - ```elixir - t("Take the elevator", to: "en-US") - # -> Take the elevator - t("Take the elevator", to: "en-GB") - # -> Take the lift - # Will first try to resolve the key in the `en-US` locale, then, since it does not exist, try `en`. - t("Create your account", to: "en-US") - # -> Create your account - t("Create your account", to: "en-GB") - # -> Create your account - ``` - - ### Fallback keys - - For scenarios where multiple keys might apply, `t/3` allows specifying a list of keys as well. - - ```elixir - t(["Create your account", "Register"], to: "en-US") - ``` - - This snippet will first try to resolve the `Create your account` key, and fall back to resolving `Register` when it does not exist. - - ### Fallback locales - - For when a key might not be available in the set locale, you can set a fallback locale. - A fallback can be either a string or a list of strings. If you set the fallback as a list, Idiom will return the translation of the first locale for which - the key is available. - - When you don't explicitly set a `fallback` for `t/3`, Idiom will try the `default_fallback` (see [Configuration](#module-configuration)). - When a key is available in neither the target **or** any of the fallback language, the key will be returned as-is. - - - ```elixir - # will return the translation for `en` - t("Key that is only available in `en` and `fr`", to: "es", fallback: "en") - # will return the translation for `fr` - t("Key that is only available in `en` and `fr`", to: "es", fallback: ["fr", "en"]) - # will return the translation for `en`, which is set as `default_fallback` - t("Key that is only available in `en` and `fr`", to: "es") - # will return "Key that is not available in any locale" - t("Key that is not available in any locale", to: "es") - ``` - - ### Using fallback keys and locales together - - When both fallback keys and locales are provided, Idiom will first try to resolve all keys in each locale before jumping to the next one. - For example, the resolution order for `t(["Create your account", "Register"], to: "es", fallback: ["fr", "de"])` will be: - - 1. `Create your account` in `es` - 2. `Register` in `es` - 3. `Create your account` in `fr` - 4. `Register` in `fr` - 5. `Create your account` in `de` - 6. `Register` in `de` - - ## Namespaces - - Idiom allows grouping your keys into namespaces. - - When calling `t/3`, Idiom looks at the following settings to determine which namespace to resolve the key in, in order of priority: - 1. The `namespace` option, like `t("Create your account", namespace: "signup")` - 2. The namespace set in the current process. You can call `Idiom.put_namespace/1` to set it. - Since this is just a wrapper around the process dictionary, it needs to be set for each process you are using Idiom in. - 3. The `default_namespace` setting. See the [Configuration](#module-configuration) section for more details on how to set it. - - ## Interpolation - - Idiom supports interpolation in messages. - Interpolation can be added by adding an interpolation key to the message, enclosing it in `{{}}`. Then, you can bind the key to any string by passing it as - key inside the second parameter of `t/3`. - - Taking the following file as an example (see also [File format](#module-file-format)): - ```json - { - "en": { - "default": { - "Welcome, {{name}}": "Welcome, {{name}}", - "It is currently {{temperature}} degrees in {{city}}": "It is currently {{temperature}} degrees in {{city}}" - } - } - } - ``` - - These messages can then be interpolated as such: - - ```elixir - t("Welcome, {{name}}", %{name: "Tim"}) - # -> Welcome, Tim - t("It is currently {{temperature}} degrees in {{city}}", %{temperature: "31", city: "Hong Kong"}) - # -> It is currently 31 degrees in Hong Kong - ``` - - ## Pluralisation - - Idiom supports the following key suffixes for pluralisation: - - - `zero` - - `one` - - `two` - - `few` - - `many` - - `other` - - Your keys, for English, might then look like this: - - ```json - { - "carrot_one": "{{count}} carrot" - "carrot_other": "{{count}} carrots" - } - ``` - - You can then pluralise your messages by passing `count` to `t/3`, such as: - - ```elixir - t("carrot", count: 1) - # -> 1 carrot - t("carrot", count: 2) - # -> 2 carrot - ``` - - > #### `{{count}}` and pluralisation {: .info} - > - > As you can see in the above example, we are not passing an extra `%{count: x}` binding. This is because the `count` option acts as a magic binding that is - > automatically available for interpolation. - - ## Backends - - Idiom is designed to be extensible with multiple over the air providers. Please see the modules in `Idiom.Backend` for the ones built-in, and always feel - free to extend the ecosystem by creating new ones. - """ + @moduledoc "README.md" + |> File.read!() + |> String.split("") + |> Enum.fetch!(1) import Idiom.Interpolation alias Idiom.Cache