Skip to content
Merged
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
20 changes: 19 additions & 1 deletion jupyterlab_server/tests/test_translation_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
get_installed_packages_locale,
get_language_pack, get_language_packs,
is_valid_locale, merge_locale_data,
run_process_and_parse)
run_process_and_parse, translator)

from .utils import maybe_patch_ioloop

Expand Down Expand Up @@ -101,6 +101,24 @@ async def test_get_locale_not_valid(jp_fetch):
assert result["data"] == {}


# --- Backend locale
# ------------------------------------------------------------------------
async def test_backend_locale(jp_fetch):
locale = "es_CO"
r = await jp_fetch("lab", "api", "translations", locale)
trans = translator.load("jupyterlab")
result = trans.__("MORE ABOUT PROJECT JUPYTER")
assert result == "Más sobre el proyecto jupyter"


async def test_backend_locale_extension(jp_fetch):
locale = "es_CO"
r = await jp_fetch("lab", "api", "translations", locale)
trans = translator.load("jupyterlab_some_package")
result = trans.__("BOOM")
assert result == "Foo bar 2"


# --- Utils testing
# ------------------------------------------------------------------------
def test_get_installed_language_pack_locales_fails():
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
msgid ""
msgstr ""
"Project-Id-Version: jupyterlab\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language-Team: Spanish\n"
"Language: es_CO\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.4.1\n"

#: /example
msgid "MORE ABOUT PROJECT JUPYTER"
msgstr "Más sobre el proyecto jupyter"
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
msgid ""
msgstr ""
"Project-Id-Version: jupyterlab_some_package\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language-Team: Spanish\n"
"Language: es_CO\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.4.1\n"

#: /example
msgid "BOOM"
msgstr "Foo bar 2"
258 changes: 254 additions & 4 deletions jupyterlab_server/translation_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
localization data.
"""

import gettext
import json
import importlib
import os
import subprocess
import sys
Expand Down Expand Up @@ -124,11 +126,8 @@ def run_process_and_parse(cmd: list):
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
result = json.loads(stdout.decode('utf-8'))

# FIXME: Use case?
result["message"] = stderr.decode('utf-8')
except Exception:
result["message"] = traceback.format_exc()
result["message"] = traceback.format_exc() + "\n" + repr(stderr.decode('utf-8'))

return result["data"], result["message"]

Expand Down Expand Up @@ -386,5 +385,256 @@ def get_language_pack(locale: str) -> tuple:
return locale_data, "\n".join(messages)


# --- Translators
# ----------------------------------------------------------------------------
class TranslationBundle:
"""
Translation bundle providing gettext translation functionality.
"""

def __init__(self, domain: str, locale: str):
self._domain = domain
self._locale = locale

self.update_locale(locale)

def update_locale(self, locale: str):
"""
Update the locale environment variables.

Parameters
----------
locale: str
The language name to use.
"""
# TODO: Need to handle packages that provide their own .mo files
self._locale = locale
localedir = None
if locale != DEFAULT_LOCALE:
language_pack_module = f"jupyterlab_language_pack_{locale}"
try:
mod = importlib.import_module(language_pack_module)
localedir = os.path.join(os.path.dirname(mod.__file__), LOCALE_DIR)
except Exception:
pass

gettext.bindtextdomain(self._domain, localedir=localedir)

def gettext(self, msgid: str) -> str:
"""
Translate a singular string.

Parameters
----------
msgid: str
The singular string to translate.

Returns
-------
str
The translated string.
"""
return gettext.dgettext(self._domain, msgid)

def ngettext(self, msgid: str, msgid_plural: str, n: int) -> str:
"""
Translate a singular string with pluralization.

Parameters
----------
msgid: str
The singular string to translate.
msgid_plural: str
The plural string to translate.
n: int
The number for pluralization.

Returns
-------
str
The translated string.
"""
return gettext.dngettext(self._domain, msgid, msgid_plural, n)

def pgettext(self, msgctxt: str, singular: str) -> str:
"""
Translate a singular string with context.

Parameters
----------
msgctxt: str
The message context.
msgid: str
The singular string to translate.

Returns
-------
str
The translated string.
"""
return gettext.dpgettext(self._domain, msgctxt, msgid)

def npgettext(self, msgctxt: str, msgid: str, msgid_plural: str, n: int) -> str:
"""
Translate a singular string with context and pluralization.

Parameters
----------
msgctxt: str
The message context.
msgid: str
The singular string to translate.
msgid_plural: str
The plural string to translate.
n: int
The number for pluralization.

Returns
-------
str
The translated string.
"""
return gettext.dnpgettext(self._domain, msgctxt, msgid, msgid_plural, n)

# Shorthands
def __(self, msgid: str) -> str:
"""
Shorthand for gettext.

Parameters
----------
msgid: str
The singular string to translate.

Returns
-------
str
The translated string.
"""
return self.gettext(msgid)

def _n(self, msgid: str, msgid_plural: str, n: int) -> str:
"""
Shorthand for ngettext.

Parameters
----------
msgid: str
The singular string to translate.
msgid_plural: str
The plural string to translate.
n: int
The number for pluralization.

Returns
-------
str
The translated string.
"""
return self.ngettext(msgid, plural, n)

def _p(self, msgctxt: str, msgid: str) -> str:
"""
Shorthand for pgettext.

Parameters
----------
msgctxt: str
The message context.
msgid: str
The singular string to translate.

Returns
-------
str
The translated string.
"""
return self.pgettext(msgctxt, msgid)

def _np(self, msgctxt: str, msgid: str, msgid_plular: str, n: str) -> str:
"""
Shorthand for npgettext.

Parameters
----------
msgctxt: str
The message context.
msgid: str
The singular string to translate.
msgid_plural: str
The plural string to translate.
n: int
The number for pluralization.

Returns
-------
str
The translated string.
"""
return self.npgettext(msgctxt, msgid, msgid_plular, n)


class translator:
"""
Translations manager.
"""
_TRANSLATORS = {}
_LOCALE = DEFAULT_LOCALE

@staticmethod
def _update_env(locale: str):
"""
Update the locale environment variables based on the settings.

Parameters
----------
locale: str
The language name to use.
"""
for key in ["LANGUAGE", "LANG"]:
os.environ[key] = f"{locale}.UTF-8"

@classmethod
def set_locale(cls, locale: str):
"""
Set locale for the translation bundles based on the settings.

Parameters
----------
locale: str
The language name to use.
"""
if is_valid_locale(locale):
cls._LOCALE = locale
translator._update_env(locale)
for domain, bundle in cls._TRANSLATORS.items():
bundle.update_locale(locale)

@classmethod
def load(cls, domain: str) -> TranslationBundle:
"""
Load translation domain.

The domain is usually the normalized ``package_name``.

Parameters
----------
domain: str
The translations domain. The normalized python package name.

Returns
-------
Translator
A translator instance bound to the domain.
"""
if domain in cls._TRANSLATORS:
trans = cls._TRANSLATORS[domain]
else:
trans = TranslationBundle(domain, cls._LOCALE)
cls._TRANSLATORS[domain] = trans

return trans


if __name__ == "__main__":
_main()
3 changes: 2 additions & 1 deletion jupyterlab_server/translations_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from tornado import gen, web

from .settings_handler import get_settings
from .translation_utils import get_language_pack, get_language_packs, is_valid_locale
from .translation_utils import get_language_pack, get_language_packs, is_valid_locale, translator

from .server import APIHandler, url_path_join

Expand Down Expand Up @@ -74,6 +74,7 @@ def get(self, locale=""):
data, message = get_language_packs(
display_locale=get_current_locale(self.lab_config))
else:
translator.set_locale(locale)
data, message = get_language_pack(locale)
if data == {} and message == "":
if is_valid_locale(locale):
Expand Down