Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.jl.cov
*.jl.mem
docs/build/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ Documentation:

This package offers facilities for [internationalization and localization (i18n and l10n)](https://en.wikipedia.org/wiki/Internationalization_and_localization) of software in the Julia programming language, using the standard [`gettext`](https://en.wikipedia.org/wiki/Gettext) system.

Essentially, Gettext.jl allows the programmer to mark user-visible messages (strings) for translation, typically by simply replacing `"..."` strings with `_"..."`. Then, translators can localize a Julia program or package by providing a list of translations in the standard `.po` format (a human-readable/editable file, supported by many software tools).
Essentially, Gettext.jl allows the programmer to mark user-visible messages (strings) for translation, typically by simply replacing `"..."` strings with `_"..."`. Then, translators can localize a Julia program or package by providing a list of translations in the standard `.po` format (a human-readable/editable file, supported by many software tools).

(This package calls GNU gettext's `libintl` library directly from Julia, via the `GettextRuntime_jll` package compiled for Julia by [Yggdrasil](https://github.com/JuliaPackaging/Yggdrasil); this is automatically installed for you by Julia's [package manager](https://github.com/JuliaLang/Pkg.jl). GNU gettext is free/open-source software licensed under the [GNU LGPL](https://www.gnu.org/software/gettext/manual/html_node/GNU-LGPL.html).)
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ makedocs(
"Home" => "index.md",
"Internationalization (i18n) API" => "i18n.md",
"Localization (l10n) and PO files" => "l10n.md",
"Reference" => "reference.md",
],
)

Expand Down
31 changes: 10 additions & 21 deletions docs/src/i18n.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,17 @@

## Marking strings for translation

The most basic i18n task is to **mark every user-visible string** for possible translation. This is typically done with the macros describe in the following subsections.
The most basic i18n task is to **mark every user-visible string** for possible translation. This is typically done with the macros described in the following subsections.

(Note: we use macros, rather than functions, so that they can automatically
substitute the current [`__GETTEXT_DOMAIN__`](@ref module_gettext) when used in a module.)
(Note: we use macros, rather than functions, so that they can automatically substitute the current [`__GETTEXT_DOMAIN__`](@ref module_gettext) when used in a module.)

See also the GNU gettext manual's tips on how to [prepare translatable strings](https://www.gnu.org/software/gettext/manual/html_node/Preparing-Strings.html) in your code. In short, they recommend **translating entire English sentences/paragraphs** (avoiding forming sentences by concatenation), splitting at paragraphs, with placeholders for interpolation/substitution (see below).

### Simple literal strings: `_"..."`

For most literal strings, you can simply replace `"..."` with [`_"..."`](@ref `@__str`), i.e. prepend an underscore.
For most literal strings, you can simply replace `"..."` with [`_"..."`](@ref @__str), i.e. prepend an underscore.

`_"..."` acts just like an ordinary Julia literal string, but internally it corresponds to a call to [`@gettext("...")`](@ref), returning a translated string if appropriate (assuming a translation exists for the current locale). The only other big difference from a typical Julia string is that [`$` interpolation](https://docs.julialang.org/en/v1/manual/strings/#string-interpolation) is **not** supported in `_"..."` (any `$` is treated literally). This is intentional: translation strings should not depend on runtime values, because a `.po` file contains only a finite number of translations, so runtime interpolation should be employed
judiciously as described below.
`_"..."` acts just like an ordinary Julia literal string, but internally it corresponds to a call to [`@gettext("...")`](@ref), returning a translated string if appropriate (assuming a translation exists for the current locale). The only other big difference from a typical Julia string is that [`$` interpolation](https://docs.julialang.org/en/v1/manual/strings/#string-interpolation) is **not** supported in `_"..."` (any `$` is treated literally). This is intentional: translation strings should not depend on runtime values, because a `.po` file contains only a finite number of translations, so runtime interpolation should be employed judiciously as described below.

### Interpolating into translated strings

Expand All @@ -40,14 +38,13 @@ In this case, a simple placeholder for `n` is not enough. Instead, you can use
```jl
@ngettext("Your birthday is in %d day.", "Your birthday is in %d days.", "%d"=>n)
```
Here, we provide both singular and plural forms of the string to be translated, and
`@ngettext` will choose one based on the runtime value of `n`. (In fact, for some languages, gettext may choose among [multiple plural forms](https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html).) The translation strings should also have a `%d` placeholder, and the `"%d"=>n` argument tells `@ngettext` to substitute `string(n)` for `%d` in the final result (using `replace`).
Here, we provide both singular and plural forms of the string to be translated, and `@ngettext` will choose one based on the runtime value of `n`. (In fact, for some languages, gettext may choose among [multiple plural forms](https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html).) The translation strings should also have a `%d` placeholder, and the `"%d"=>n` argument tells `@ngettext` to substitute `string(n)` for `%d` in the final result (using `replace`).

If you want to perform more complicated numeric formatting on your own, e.g. with `Printf`, you can pass `n` instead of `"%d"=>n` and no substitution will be performed on the result.

### Providing additional translation context: `@pgettext`

Sometimes, the *same string* might be used in different contexts in a program that require *different translations*. This is especially common for very short strings (e.g. single words). For example, the string `"Open"` in a File menu might be translated into Spanish as a verb `"Abrir"` (*to open* a file), but the same string might be translated as an adjective `"Abierto"` to indicate that a door is *open* in a game.
Sometimes, the *same string* might be used in different contexts in a program that require *different translations*. This is especially common for very short strings (e.g. single words). For example, the string `"Open"` in a File menu might be translated into Spanish as a verb `"Abrir"` (*to open* a file), but the same string might be translated as an adjective `"Abierto"` to indicate that a door is *open* in a game.

To support this case, Gettext allows you to pass an additional *context* string for a translation, using the macro [`@pgettext`](@ref). For example:

Expand All @@ -56,8 +53,7 @@ To support this case, Gettext allows you to pass an additional *context* string
@pgettext("Door", "Open") # translate an "Open" sign attached to a door
```

Similarly, there is a macro [`@npgettext`](@ref) that is like `@ngettext` but
has an additional context string as the first argument.
Similarly, there is a macro [`@npgettext`](@ref) that is like `@ngettext` but has an additional context string as the first argument.

### Macro reference
The following are the string-i18n macros:
Expand All @@ -73,7 +69,7 @@ Gettext.@N__str

## [Domains](@id domains)

Every translation in Gettext is relative to a "domain", which usually corresponds to a single program or package. Each domain has a list of strings to be translated, and can have `domain.po` files that give translations for particular locales (see [Localization (l10n) and PO files](@ref)). To control the domain being used, you need to do two things:
Every translation in Gettext is relative to a "domain", which usually corresponds to a single program or package. Each domain has a list of strings to be translated, and can have `domain.po` files that give translations for particular locales (see [Localization (l10n) and PO files](@ref)). To control the domain being used, you need to do two things:

1. Call [`bindtextdomain`](@ref) to specify the path of the `po` directory containing translations for that domain. This is typically done in a module's `__init__` function (see below).
2. Specify the domain you are using: this is done via the [`__GETTEXT_DOMAIN__`](@ref module_gettext) constant in modules/packages (below), or is done by setting a global domain with [`textdomain`](@ref) for code running in Julia's [`Main`](https://docs.julialang.org/en/v1/base/base/#Main) module (scripts and interactive work).
Expand Down Expand Up @@ -106,18 +102,11 @@ end
end
```

In `"MyPackage-<uuid>"`, the `<uuid>` denotes the unique [UUID
identifier of your package](https://pkgdocs.julialang.org/v1/toml-files/#The-uuid-field) — this ensures that two packages will not have the same gettext domain, even if they happen to have the same name.
In `"MyPackage-<uuid>"`, the `<uuid>` denotes the unique [UUID identifier of your package](https://pkgdocs.julialang.org/v1/toml-files/#The-uuid-field) — this ensures that two packages will not have the same gettext domain, even if they happen to have the same name.

When they are used in *any* module (other than Julia's implicit [`Main`](https://docs.julialang.org/en/v1/base/base/#Main) module for scripts and interactive work), the macros, `_"..."`, `@gettext`, and so on (see above) pass this global variable `__GETTEXT_DOMAIN__` to the corresponding low-level functions. You will get an [`UndefVarError`](https://docs.julialang.org/en/v1/base/base/#Core.UndefVarError) if you use those macros in a module that does not define `__GETTEXT_DOMAIN__`. (In the `Main` module, the same macros instead use the global [`textdomain`](@ref).)

The `bindtextdomain` call in the example above assumes that you
have a top-level directory `po` in your package, which is a good
default location. This directory is used to store translation
(`.po`) files `po/<locale>/LC_MESSAGES/MyModule-<uuid>.po`, where
`<locale>` is the standard locale identifier, e.g. `en` (English) or
`en_GB` (English, Great Britain), and `MyModule-<uuid>` is your
domain name from above.
The `bindtextdomain` call in the example above assumes that you have a top-level directory `po` in your package, which is a good default location. This directory is used to store translation (`.po`) files `po/<locale>/LC_MESSAGES/MyModule-<uuid>.po`, where `<locale>` is the standard locale identifier, e.g. `en` (English) or `en_GB` (English, Great Britain), and `MyModule-<uuid>` is your domain name from above.

If your package has submodules, in most cases they can simply employ the same domain as your top-level module `MyModule`, via:

Expand Down
24 changes: 8 additions & 16 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ This package offers facilities for [internationalization and localization (i18n

`gettext` is a popular system, dating back to 1990, for i18n and l10n of **messages** (strings) exposed to users in a program's interface: prompts, menu items, error messages, and so on. This consists of two parts:

* **i18n**: in your program, *any string that might need translation* should be wrapped in a call to a `gettext` function. In Gettext.jl, this is usually accomplished by macros: For a typical string `"..."`, you simply replace it with [`_"..."`](@ref `@__str`) to make it translatable. There are also more specialized macros, such as [`@ngettext`](@ref) for strings with runtime-dependent singular and plural forms. See the [Internationalization (i18n) API](@ref) chapter.
* **i18n**: in your program, *any string that might need translation* should be wrapped in a call to a `gettext` function. In Gettext.jl, this is usually accomplished by macros: For a typical string `"..."`, you simply replace it with [`_"..."`](@ref @__str) to make it translatable. There are also more specialized macros, such as [`@ngettext`](@ref) for strings with runtime-dependent singular and plural forms. See the [Internationalization (i18n) API](@ref) chapter.

* **l10n**: for any locale, one can create a `.po` file that lists the translations of strings in a human-readable text format — this format is designed so that non-programmers can easily contribute translations, and there are many software tools to help create `.po` files (either manually or via automated translation). These `.po` files are then placed in a standardized directory for your package, and are converted to a binary `.mo` format with the [GNU `msgfmt` program](https://www.gnu.org/software/gettext/manual/html_node/Binaries.html). At runtime, Gettext.jl then automatically looks up translations (if any) from the current locale (as indicated by the operating system) and substitutes them for strings like `_"..."` in your program. See the [Localization (l10n) and PO files](@ref) chapter.
* **l10n**: for any locale, one can create a `.po` file that lists the translations of strings in a human-readable text format — this format is designed so that non-programmers can easily contribute translations, and there are many software tools to help create `.po` files (either manually or via automated translation). These `.po` files are then placed in a standardized directory for your package, and are converted to a binary `.mo` format with the [GNU `msgfmt` program](https://www.gnu.org/software/gettext/manual/html_node/Binaries.html). At runtime, Gettext.jl then automatically looks up translations (if any) from the current locale (as indicated by the operating system) and substitutes them for strings like `_"..."` in your program. See the [Localization (l10n) and PO files](@ref) chapter.

(Other forms of i18n and l10n, such as locale-specific formatting of numbers and dates, are outside the scope of `gettext`, but are provided by other libraries such as `libc`.)

Expand All @@ -29,32 +29,24 @@ To i18n it, the first step is simply to change the code to:
using Gettext
println(_"Hello, world!")
```
which tells Gettext.jl to translate the string `"Hello, world!"` for the current locale, if possible. By default, if no translation is found, [`_"..."`](@ref `@__str`) will simply return the original untranslated string, and the program will have the same output as before.
which tells Gettext.jl to translate the string `"Hello, world!"` for the current locale, if possible. By default, if no translation is found, [`_"..."`](@ref @__str) will simply return the original untranslated string, and the program will have the same output as before.

The Gettext.jl package comes with a sample `.po` translation file that includes a translation of `"Hello, world!"` into French. In particular, the Gettext.jl package has a text file `po/fr/LC_MESSAGES/sample.po` (along with its binary-format equivalent `po/fr/LC_MESSAGES/sample.mo`) that includes the translation:
The Gettext.jl package comes with a sample `.po` translation file that includes a translation of `"Hello, world!"` into French. In particular, the Gettext.jl package has a text file `po/fr/LC_MESSAGES/sample.po` (along with its binary-format equivalent `po/fr/LC_MESSAGES/sample.mo`) that includes the translation:
```
msgid "Hello, world!"
msgstr "Bonjour le monde !"
```
Here, in the `sample.po` file, the `msgid` is the original string and `msgstr` is the translation. The `po` directory (typically at the top level of the package) is where a package's translations are placed, and `po/fr/LC_MESSAGES` contains translations for French-language (`fr`) locales in the default "category" `LC_MESSAGES`. Inside this directory, `sample.po` contains the translations for the "domain" we called `"sample"` — there will typically be one such domain per independent package/component of a program (see [Gettext for modules and packages](@ref module_gettext)). We need to tell Gettext.jl where to find the translations we are using, which we could do here via:
Here, in the `sample.po` file, the `msgid` is the original string and `msgstr` is the translation. The `po` directory (typically at the top level of the package) is where a package's translations are placed, and `po/fr/LC_MESSAGES` contains translations for French-language (`fr`) locales in the default "category" `LC_MESSAGES`. Inside this directory, `sample.po` contains the translations for the "domain" we called `"sample"` — there will typically be one such domain per independent package/component of a program (see [Gettext for modules and packages](@ref module_gettext)). We need to tell Gettext.jl where to find the translations we are using, which we could do here via:
```jl
using Gettext
bindtextdomain("sample", joinpath(dirname(pathof(Gettext)), "..", "po"))
textdomain("sample") # set domain for the global Main module only

println(_"Hello, world!")
```
Here, [`bindtextdomain`](@ref) specifies the path of the `po` directory for
a given domain. For scripts (or interactive sessions) running in Julia's
[`Main`](https://docs.julialang.org/en/v1/base/base/#Main) module, you then call
[`textdomain`](@ref) to set the global domain. Inside packages and
other modules, you instead define a [`__GETTEXT_DOMAIN__`](@ref module_gettext) global to set a package-specific domain, so that each package can have independent
translations.

Now, when you run the code, you will *still* see `"Hello, world!"` if you are in any
non-French locale, but *French* locales will instead print `"Bonjour le monde !"`.
On Unix-like systems, you can set the locale simply via the [`LANGUAGE` environment
variable](https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.htmls). This can even be changed at runtime, so
Here, [`bindtextdomain`](@ref) specifies the path of the `po` directory for a given domain. For scripts (or interactive sessions) running in Julia's [`Main`](https://docs.julialang.org/en/v1/base/base/#Main) module, you then call [`textdomain`](@ref) to set the global domain. Inside packages and other modules, you instead define a [`__GETTEXT_DOMAIN__`](@ref module_gettext) global to set a package-specific domain, so that each package can have independent translations.

Now, when you run the code, you will *still* see `"Hello, world!"` if you are in any non-French locale, but *French* locales will instead print `"Bonjour le monde !"`. On Unix-like systems, you can set the locale simply via the [`LANGUAGE` environment variable](https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.htmls). This can even be changed at runtime, so
```jl
using Gettext
bindtextdomain("sample", joinpath(dirname(pathof(Gettext)), "..", "po"))
Expand Down
Loading
Loading