diff --git a/jupyter-notebook-gui-translation/jupyter-notebook-gui-translation.md b/jupyter-notebook-gui-translation/jupyter-notebook-gui-translation.md new file mode 100644 index 00000000..3750e1b5 --- /dev/null +++ b/jupyter-notebook-gui-translation/jupyter-notebook-gui-translation.md @@ -0,0 +1,224 @@ +# Jupyter Notebook Translation and Localization + +## Problem + +There is currently no standard approach for translating the GUI of [Jupyter notebook]( https://github.com/jupyter/notebook). +This has driven some people to do a +[single language translation for Jupyter 4.1](https://twitter.com/Mbussonn/status/685870031247400960). + +For information: previous attempts and related issues: + +- https://github.com/ipython/ipython/issues/6718 +- https://github.com/ipython/ipython/pull/5922 +- https://github.com/jupyter/notebook/issues/870 + +## Scope +The proposed enhancement is for "classic" [Jupyter notebook]( https://github.com/jupyter/notebook), +not [Jupyter lab](https://github.com/jupyterlab/jupyterlab). +Hopefully, some of the concepts used here will carry over, but for now the scope here is limited to classic +[Jupyter notebook]( https://github.com/jupyter/notebook). + +## Proposed Enhancement + +Use [Babel](http://babel.pocoo.org/en/latest/) +to extract translatable strings from the Jupyter code, creating `.pot` files that can be updated +whenever the code base changes. The `.pot` file can be thought of as the source from which all translations are derived. + +Translators and/or interested contributors can then use utilities such as [Poedit](https://poedit.net/) to create +translated `.po` files from the master `.pot` file for the desired languages. + +At install time, convert the translated `.po` into two runtime formats: +* Convert to `.mo` which can be used by Python code using the gettext() APIs in Python, and can also be used by +the i18n extensions in [Jinja2](http://jinja.pocoo.org/docs/dev/extensions/#i18n-extension) + +* Convert to JSON using [po2json](https://github.com/mikeedwards/po2json) for consumption by the Javascript +code within Jupyter. + +## Detailed Explanation + +The [Jupyter notebook code]( https://github.com/jupyter/notebook) presents a significant challenge in terms of enablement for translation, +mostly because there are multiple different types of source code from which translatable UI strings +are derived. + +In [Jupyter notebook]( https://github.com/jupyter/notebook), translatable strings can come from one of three places: + +1. Directly from Python code + +2. As part of a Jinja2 HTML template, which is consumed by Python code + +3. From Javascript code + +For each of these three types, it is necessary to follow a few simple steps in order to allow the code +to work properly in a translated environment. These steps are: + +1. Use an established API to identify those strings in the source code that are presented as UI +and thus should be translatable. +2. Provide the hooks in the source code that will allow the code to access translated strings +at run time. + +Once these have been done, the [Babel](http://babel.pocoo.org/en/latest/) utilities provide an easy to +use mechanism to identify the files in the Jupyter code base that contain translatable strings, and +extract all of them into a single file ( a `.pot` file ) that is used as the basis for translation. + +Let's look at how this would look for each of the three types mentioned above: + +### Python files + +Some UI strings in the Jupyter notebook come directly from Python code. For these strings, the most +widely accepted way to make them translatable is to use Python's gettext() API. When using gettext(), +the developer simply needs to enclose the translatable Python string within _(), and add the appropriate +calls in the Python to retrieve that string from the message catalog at run time. So for example: + +```python +return info + "The Jupyter Notebook is running at: %s" % self.display_url +``` + +becomes + +```python +return info + _("The Jupyter Notebook is running at: %s") % self.display_url +``` + +After this step is complete, then hooks must be put in place in order to tell Python to use gettext() +to retrieve the string from the message catalog at runtime. This is simply a matter of adding +```python +import gettext +``` +at the top of the python code and then adding +```python +# Set up message catalog access +trans = gettext.translation('notebook', localedir=os.path.join(base_dir, 'locale'), fallback=True) +trans.install() +``` + +Once this is complete, any calls to `_()` in the code will retrieve a translated string from +${base_dir}/locale/**xx**/LC_MESSAGES/notebook.mo, where **xx** is the language code in use +when the notebook is launched. +For example "de" for German, "fr" for French, etc. If no message catalog is available, or if +the string doesn't exist in the catalog, then the string passed as the argument to _() is +returned. + +In this context, **${base_dir}** refers to the base installation directory for notebook. We are +assuming that all provided translations will be small enough so that they can be shipped +with notebook itself instead of having to be split out into separately installable packages. + + +### HTML Templates +The majority of the language of the GUI is contained in [html template files](https://github.com/jupyter/notebook/tree/master/notebook/templates) +and accessed via [Jinja2](http://jinja.pocoo.org). + +For the HTML templates, I recommend that we use the [Jinja2 i18n extension] +(http://jinja.pocoo.org/docs/dev/extensions/#i18n-extension) that allows us to specify which portions of each template contain translatable +strings, and is quite compatible with gettext() as described above. +The extension contains some features that allow for things like variable substitution and simple plural handling, +but in it's simplest form it uses tags `{% trans %}` and `{% endtrans %}` to delimit those strings that are translatable. +Thus, the message at the top of the first screen you see when starting Jupyter looks like this in the template: + +```html +