Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package jupyter-jupyterlab-server for 
openSUSE:Factory checked in at 2021-09-22 22:13:04
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/jupyter-jupyterlab-server (Old)
 and      /work/SRC/openSUSE:Factory/.jupyter-jupyterlab-server.new.1899 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "jupyter-jupyterlab-server"

Wed Sep 22 22:13:04 2021 rev:6 rq:920994 version:2.8.1

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/jupyter-jupyterlab-server/jupyter-jupyterlab-server.changes
      2021-08-23 10:08:36.244216017 +0200
+++ 
/work/SRC/openSUSE:Factory/.jupyter-jupyterlab-server.new.1899/jupyter-jupyterlab-server.changes
    2021-09-22 22:13:26.444347143 +0200
@@ -1,0 +2,24 @@
+Fri Sep 17 09:06:02 UTC 2021 - Ben Greiner <[email protected]>
+
+- Update to 2.8.2
+  * Fall back to DEFAULT_LOCALE when translation settings schema is
+    invalid in get_current_locale
+  * Translate settings schema
+- Really remove the patch jupyterlab-server-pr198-openapi014.patch
+
+-------------------------------------------------------------------
+Mon Sep  6 17:27:13 UTC 2021 - Ben Greiner <[email protected]>
+
+- Update to 2.7.2
+  * Do not overwrite capitalization of region names #202
+  * Use Check Links Action #201
+  * Recommend pytest --pyargs jupyterlab_server #203
+- Changlog for release 2.7.1
+  * Fix reset user settings if validation failed #199
+  * support openapi-core 0.14 SpecPath #198
+- Drop jupyterlab-server-pr198-openapi014.patch merged upstream
+  * gh#jupyterlab/jupyterlab_server#198
+- Fix jupyter-jupyterlab-server-rpmlintrc for rpmlint 2 errors
+- All the test exceptions have been resolved upstream
+
+-------------------------------------------------------------------

Old:
----
  jupyterlab-server-pr198-openapi014.patch
  jupyterlab_server-2.7.0.tar.gz

New:
----
  jupyterlab_server-2.8.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ jupyter-jupyterlab-server.spec ++++++
--- /var/tmp/diff_new_pack.VagAaL/_old  2021-09-22 22:13:26.968347579 +0200
+++ /var/tmp/diff_new_pack.VagAaL/_new  2021-09-22 22:13:26.972347582 +0200
@@ -19,15 +19,13 @@
 %define skip_python2 1
 %define oldpython python
 Name:           jupyter-jupyterlab-server
-Version:        2.7.0
+Version:        2.8.1
 Release:        0
 Summary:        Server components for JupyterLab and JupyterLab-like 
applications
 License:        BSD-3-Clause
 URL:            https://github.com/jupyterlab/jupyterlab_server
 Source:         
https://files.pythonhosted.org/packages/source/j/jupyterlab_server/jupyterlab_server-%{version}.tar.gz
 Source100:      jupyter-jupyterlab-server-rpmlintrc
-# PATCH-FIX-UPSTREAM jupyterlab-server-pr198-openapi014.patch -- 
gh#jupyterlab/jupyterlab_server#198
-Patch0:         jupyterlab-server-pr198-openapi014.patch
 BuildRequires:  %{python_module Babel}
 BuildRequires:  %{python_module Jinja2 >= 2.10}
 BuildRequires:  %{python_module base >= 3.5}
@@ -101,12 +99,7 @@
 %python_expand %fdupes %{buildroot}%{$python_sitelib}
 
 %check
-# we dont have the language packs installed, supposed to be tested here
-donttest+=" or (test_translation_api and (get_locale or backend_locale or 
get_installed or get_language) and not (_not_ or bad or fails))"
-# flaky
-donttest+=" or (test_workspaces_api and test_get_non_existant)"
-mv jupyterlab_server/tests/conftest.py ./
-%pytest -ra -k "not (${donttest:4})"
+%pytest --pyargs jupyterlab_server -ra
 
 %files %{python_files}
 %license LICENSE

++++++ jupyter-jupyterlab-server-rpmlintrc ++++++
--- /var/tmp/diff_new_pack.VagAaL/_old  2021-09-22 22:13:27.000347605 +0200
+++ /var/tmp/diff_new_pack.VagAaL/_new  2021-09-22 22:13:27.000347605 +0200
@@ -1,4 +1,4 @@
-# This is an importan test file.
+# This is an important test file.
 # The tests don't work without it, and other packages' tests depend on these 
tests being available
 addFilter("backup-file-in-package 
.*/jupyterlab_server/tests/schemas/@jupyterlab/shortcuts-extension/package.json.orig")
-addFilter("suse-filelist-forbidden 
.*/jupyterlab_server/tests/schemas/@jupyterlab/shortcuts-extension/package.json.orig")
+addFilter("filelist-forbidden 
.*/jupyterlab_server/tests/schemas/@jupyterlab/shortcuts-extension/package.json.orig")

++++++ jupyterlab_server-2.7.0.tar.gz -> jupyterlab_server-2.8.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyterlab_server-2.7.0/CHANGELOG.md 
new/jupyterlab_server-2.8.1/CHANGELOG.md
--- old/jupyterlab_server-2.7.0/CHANGELOG.md    2021-08-11 16:05:01.000000000 
+0200
+++ new/jupyterlab_server-2.8.1/CHANGELOG.md    2021-09-07 20:22:44.000000000 
+0200
@@ -6,6 +6,76 @@
 
 <!-- <START NEW CHANGELOG ENTRY> -->
 
+## 2.8.1
+
+([Full 
Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.8.0...680f4fe1c8c7d1d7841e14562720d81a27e6d0ad))
+
+### Bugs fixed
+
+- Fall back to `DEFAULT_LOCALE` when translation settings schema is invalid in 
`get_current_locale` 
[#207](https://github.com/jupyterlab/jupyterlab_server/pull/207) 
([@telamonian](https://github.com/telamonian))
+
+### Contributors to this release
+
+([GitHub contributors page for this 
release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-09-07&to=2021-09-07&type=c))
+
+[@telamonian](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Atelamonian+updated%3A2021-09-07..2021-09-07&type=Issues)
+
+<!-- <END NEW CHANGELOG ENTRY> -->
+
+## 2.8.0
+
+([Full 
Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.7.2...407a8e997825e76b7f9c8992ee03206c21ea0fa0))
+
+### Enhancements made
+
+- Translate settings schema 
[#205](https://github.com/jupyterlab/jupyterlab_server/pull/205) 
([@fcollonval](https://github.com/fcollonval))
+
+### Contributors to this release
+
+([GitHub contributors page for this 
release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-08-23&to=2021-09-07&type=c))
+
+[@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2021-08-23..2021-09-07&type=Issues)
+
+## 2.7.2
+
+([Full 
Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.7.1...1508ff86421f1473ad388ac2d384bf986e326862))
+
+### Bugs fixed
+
+- Do not overwrite capitalization of region names 
[#202](https://github.com/jupyterlab/jupyterlab_server/pull/202) 
([@krassowski](https://github.com/krassowski))
+
+### Maintenance and upkeep improvements
+
+- Use Check Links Action 
[#201](https://github.com/jupyterlab/jupyterlab_server/pull/201) 
([@blink1073](https://github.com/blink1073))
+
+### Documentation improvements
+
+- Recommend `pytest --pyargs jupyterlab_server` 
[#203](https://github.com/jupyterlab/jupyterlab_server/pull/203) 
([@krassowski](https://github.com/krassowski))
+
+### Contributors to this release
+
+([GitHub contributors page for this 
release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-08-17&to=2021-08-23&type=c))
+
+[@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2021-08-17..2021-08-23&type=Issues)
 | 
[@codecov-commenter](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Acodecov-commenter+updated%3A2021-08-17..2021-08-23&type=Issues)
 | 
[@krassowski](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Akrassowski+updated%3A2021-08-17..2021-08-23&type=Issues)
+
+## 2.7.1
+
+([Full 
Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.7.0...4c5f9c84fa4c1be2267712d2a5f28fe82bdf9047))
+
+### Bugs fixed
+
+- Fix reset user settings if validation failed 
[#199](https://github.com/jupyterlab/jupyterlab_server/pull/199) 
([@fcollonval](https://github.com/fcollonval))
+
+### Maintenance and upkeep improvements
+
+- TST: support openapi-core 0.14 SpecPath 
[#198](https://github.com/jupyterlab/jupyterlab_server/pull/198) 
([@bnavigator](https://github.com/bnavigator))
+
+### Contributors to this release
+
+([GitHub contributors page for this 
release](https://github.com/jupyterlab/jupyterlab_server/graphs/contributors?from=2021-08-11&to=2021-08-17&type=c))
+
+[@blink1073](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Ablink1073+updated%3A2021-08-11..2021-08-17&type=Issues)
 | 
[@bnavigator](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Abnavigator+updated%3A2021-08-11..2021-08-17&type=Issues)
 | 
[@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2021-08-11..2021-08-17&type=Issues)
 | 
[@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Awelcome+updated%3A2021-08-11..2021-08-17&type=Issues)
+
 ## 2.7.0
 
 ([Full 
Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.6.2...919186b026f8c86f2f14c11318776e272a9dd629))
@@ -20,8 +90,6 @@
 
 
[@fcollonval](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Afcollonval+updated%3A2021-08-03..2021-08-11&type=Issues)
 | 
[@welcome](https://github.com/search?q=repo%3Ajupyterlab%2Fjupyterlab_server+involves%3Awelcome+updated%3A2021-08-03..2021-08-11&type=Issues)
 
-<!-- <END NEW CHANGELOG ENTRY> -->
-
 ## 2.6.2
 
 ([Full 
Changelog](https://github.com/jupyterlab/jupyterlab_server/compare/v2.6.1...1ac80fc439a8150de11bc470d370693cf5389781))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyterlab_server-2.7.0/CONTRIBUTING.md 
new/jupyterlab_server-2.8.1/CONTRIBUTING.md
--- old/jupyterlab_server-2.7.0/CONTRIBUTING.md 2021-08-11 16:05:01.000000000 
+0200
+++ new/jupyterlab_server-2.8.1/CONTRIBUTING.md 2021-09-07 20:22:44.000000000 
+0200
@@ -16,5 +16,5 @@
 git clone https://github.com/jupyterlab/jupyterlab_server.git
 cd jupyterlab_server
 pip install -e .[test]  # install test dependencies
-pytest
+pytest --pyargs jupyterlab_server
 ```
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyterlab_server-2.7.0/PKG-INFO 
new/jupyterlab_server-2.8.1/PKG-INFO
--- old/jupyterlab_server-2.7.0/PKG-INFO        2021-08-11 16:05:38.722334000 
+0200
+++ new/jupyterlab_server-2.8.1/PKG-INFO        2021-09-07 20:23:32.172667000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: jupyterlab_server
-Version: 2.7.0
+Version: 2.8.1
 Summary: A set of server components for JupyterLab and JupyterLab like 
applications .
 Home-page: https://jupyter.org
 Author: Jupyter Development Team
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyterlab_server-2.7.0/jupyterlab_server/_version.py 
new/jupyterlab_server-2.8.1/jupyterlab_server/_version.py
--- old/jupyterlab_server-2.7.0/jupyterlab_server/_version.py   2021-08-11 
16:05:24.000000000 +0200
+++ new/jupyterlab_server-2.8.1/jupyterlab_server/_version.py   2021-09-07 
20:23:14.000000000 +0200
@@ -4,7 +4,7 @@
 """
 import re
 
-__version__ = '2.7.0'
+__version__ = '2.8.1'
 
 # Build up version_info tuple for backwards compatibility
 pattern = r'(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyterlab_server-2.7.0/jupyterlab_server/handlers.py 
new/jupyterlab_server-2.8.1/jupyterlab_server/handlers.py
--- old/jupyterlab_server-2.7.0/jupyterlab_server/handlers.py   2021-08-11 
16:05:01.000000000 +0200
+++ new/jupyterlab_server-2.8.1/jupyterlab_server/handlers.py   2021-09-07 
20:22:44.000000000 +0200
@@ -201,6 +201,19 @@
         setting_path = ujoin(extension_app.settings_url, '(?P<schema_name>.+)')
         handlers.append((setting_path, SettingsHandler, settings_config))
 
+        # Handle translations.
+        ## Translations requires settings as the locale source of truth is 
stored in it
+        if extension_app.translations_api_url:
+            # Handle requests for the list of language packs available.
+            # Make slash optional.
+            translations_path = ujoin(extension_app.translations_api_url, '?')
+            handlers.append((translations_path, TranslationsHandler, 
settings_config))
+
+            # Handle requests for an individual language pack.
+            translations_lang_path = ujoin(
+                extension_app.translations_api_url, '(?P<locale>.*)')
+            handlers.append((translations_lang_path, TranslationsHandler, 
settings_config))
+
     # Handle saved workspaces.
     if extension_app.workspaces_dir:
 
@@ -279,18 +292,6 @@
             }
         ))
 
-    # Handle translations.
-    if extension_app.translations_api_url:
-        # Handle requests for the list of language packs available.
-        # Make slash optional.
-        translations_path = ujoin(extension_app.translations_api_url, '?')
-        handlers.append((translations_path, TranslationsHandler, 
{'lab_config': extension_app}))
-
-        # Handle requests for an individual language pack.
-        translations_lang_path = ujoin(
-            extension_app.translations_api_url, '(?P<locale>.*)')
-        handlers.append((translations_lang_path, TranslationsHandler, 
{'lab_config': extension_app}))
-
     # Let the lab handler act as the fallthrough option instead of a 404.
     fallthrough_url = ujoin(extension_app.app_url, r'.*')
     handlers.append((fallthrough_url, NotFoundHandler))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyterlab_server-2.7.0/jupyterlab_server/settings_handler.py 
new/jupyterlab_server-2.8.1/jupyterlab_server/settings_handler.py
--- old/jupyterlab_server-2.7.0/jupyterlab_server/settings_handler.py   
2021-08-11 16:05:01.000000000 +0200
+++ new/jupyterlab_server-2.8.1/jupyterlab_server/settings_handler.py   
2021-09-07 20:22:44.000000000 +0200
@@ -3,355 +3,47 @@
 # Copyright (c) Jupyter Development Team.
 # Distributed under the terms of the Modified BSD License.
 import json
-import os
-from glob import glob
 
-import json5
-from jsonschema import Draft4Validator as Validator
 from jsonschema import ValidationError
-from jupyter_server.extension.handler import ExtensionHandlerJinjaMixin, 
ExtensionHandlerMixin
-from jupyter_server.services.config.manager import ConfigManager, 
recursive_update
+from jupyter_server.extension.handler import (
+    ExtensionHandlerJinjaMixin,
+    ExtensionHandlerMixin,
+)
 from tornado import web
 
-from .server import APIHandler, tz
+from .settings_utils import SchemaHandler, get_settings, save_settings
+from .translation_utils import DEFAULT_LOCALE, translator
 
-# The JupyterLab settings file extension.
-SETTINGS_EXTENSION = '.jupyterlab-settings'
 
-
-def _get_schema(schemas_dir, schema_name, overrides, labextensions_path):
-    """Returns a dict containing a parsed and validated JSON schema."""
-    notfound_error = 'Schema not found: %s'
-    parse_error = 'Failed parsing schema (%s): %s'
-    validation_error = 'Failed validating schema (%s): %s'
-
-    path = None
-
-    # Look for the setting in all of the labextension paths first
-    # Use the first one
-    if labextensions_path is not None:
-        ext_name, _, plugin_name = schema_name.partition(':')
-        for ext_path in labextensions_path:
-            target = os.path.join(ext_path, ext_name, 'schemas', ext_name, 
plugin_name + '.json')
-            if os.path.exists(target):
-                schemas_dir = os.path.join(ext_path, ext_name, 'schemas')
-                path = target
-                break
-
-    # Fall back on the default location
-    if path is None:
-        path = _path(schemas_dir, schema_name)
-
-    if not os.path.exists(path):
-        raise web.HTTPError(404, notfound_error % path)
-
-    with open(path, encoding='utf-8') as fid:
-        # Attempt to load the schema file.
-        try:
-            schema = json.load(fid)
-        except Exception as e:
-            name = schema_name
-            raise web.HTTPError(500, parse_error % (name, str(e)))
-
-    schema = _override(schema_name, schema, overrides)
-
-    # Validate the schema.
-    try:
-        Validator.check_schema(schema)
-    except Exception as e:
-        name = schema_name
-        raise web.HTTPError(500, validation_error % (name, str(e)))
-
-    version = _get_version(schemas_dir, schema_name)
-
-    return schema, version
-
-
-def _get_user_settings(settings_dir, schema_name, schema):
-    """
-    Returns a dictionary containing the raw user settings, the parsed user
-    settings, a validation warning for a schema, and file times.
-    """
-    path = _path(settings_dir, schema_name, False, SETTINGS_EXTENSION)
-    raw = '{}'
-    settings = {}
-    warning = None
-    validation_warning = 'Failed validating settings (%s): %s'
-    parse_error = 'Failed loading settings (%s): %s'
-    last_modified = None
-    created = None
-
-    if os.path.exists(path):
-        stat = os.stat(path)
-        last_modified = tz.utcfromtimestamp(stat.st_mtime).isoformat()
-        created = tz.utcfromtimestamp(stat.st_ctime).isoformat()
-        with open(path, encoding='utf-8') as fid:
-            try:  # to load and parse the settings file.
-                raw = fid.read() or raw
-                settings = json5.loads(raw)
-            except Exception as e:
-                raise web.HTTPError(500, parse_error % (schema_name, str(e)))
-
-    # Validate the parsed data against the schema.
-    if len(settings):
-        validator = Validator(schema)
-        try:
-            validator.validate(settings)
-        except ValidationError as e:
-            warning = validation_warning % (schema_name, str(e))
-            raw = '{}'
-
-    return dict(
-        raw=raw,
-        settings=settings,
-        warning=warning,
-        last_modified=last_modified,
-        created=created
-    )
-
-
-def _get_version(schemas_dir, schema_name):
-    """Returns the package version for a given schema or 'N/A' if not found."""
-
-    path = _path(schemas_dir, schema_name)
-    package_path = os.path.join(os.path.split(path)[0], 'package.json.orig')
-
-    try:  # to load and parse the package.json.orig file.
-        with open(package_path, encoding='utf-8') as fid:
-            package = json.load(fid)
-            return package['version']
-    except Exception:
-        return 'N/A'
-
-
-def _list_settings(schemas_dir, settings_dir, overrides, extension='.json', 
labextensions_path=None):
-    """
-    Returns a tuple containing:
-     - the list of plugins, schemas, and their settings,
-       respecting any defaults that may have been overridden.
-     - the list of warnings that were generated when
-       validating the user overrides against the schemas.
-    """
-
-    settings = {}
-    federated_settings = {}
-    warnings = []
-
-    if not os.path.exists(schemas_dir):
-        warnings = ['Settings directory does not exist at %s' % schemas_dir]
-        return ([], warnings)
-
-    schema_pattern = schemas_dir + '/**/*' + extension
-    schema_paths = [path for path in glob(schema_pattern, recursive=True)]
-    schema_paths.sort()
-
-    for schema_path in schema_paths:
-        # Generate the schema_name used to request individual settings.
-        rel_path = os.path.relpath(schema_path, schemas_dir)
-        rel_schema_dir, schema_base = os.path.split(rel_path)
-        id = schema_name = ':'.join([
-            rel_schema_dir,
-            schema_base[:-len(extension)]  # Remove file extension.
-        ]).replace('\\', '/')               # Normalize slashes.
-        schema, version = _get_schema(schemas_dir, schema_name, overrides, 
None)
-        user_settings = _get_user_settings(settings_dir, schema_name, schema)
-
-        if user_settings["warning"]:
-            warnings.append(user_settings.pop('warning'))
-
-        # Add the plugin to the list of settings.
-        settings[id] = dict(
-            id=id,
-            schema=schema,
-            version=version,
-            **user_settings
+class SettingsHandler(ExtensionHandlerMixin, ExtensionHandlerJinjaMixin, 
SchemaHandler):
+    def initialize(
+        self,
+        name,
+        app_settings_dir,
+        schemas_dir,
+        settings_dir,
+        labextensions_path,
+        **kwargs
+    ):
+        SchemaHandler.initialize(
+            self, app_settings_dir, schemas_dir, settings_dir, 
labextensions_path
         )
-
-    if labextensions_path is not None:
-        schema_paths = []
-        for ext_dir in labextensions_path:
-            schema_pattern = ext_dir + '/**/schemas/**/*' + extension
-            schema_paths.extend([path for path in glob(schema_pattern, 
recursive=True)])
-
-        schema_paths.sort()
-
-        for schema_path in schema_paths:
-            schema_path = schema_path.replace(os.sep, '/')
-
-            base_dir, rel_path = schema_path.split('schemas/')
-
-            # Generate the schema_name used to request individual settings.
-            rel_schema_dir, schema_base = os.path.split(rel_path)
-            id = schema_name = ':'.join([
-                rel_schema_dir,
-                schema_base[:-len(extension)]  # Remove file extension.
-            ]).replace('\\', '/')               # Normalize slashes.
-
-            # bail if we've already handled the highest federated setting
-            if id in federated_settings:
-                continue
-
-            schema, version = _get_schema(schemas_dir, schema_name, overrides, 
labextensions_path=labextensions_path)
-            user_settings = _get_user_settings(settings_dir, schema_name, 
schema)
-
-            if user_settings["warning"]:
-                warnings.append(user_settings.pop('warning'))
-
-            # Add the plugin to the list of settings.
-            federated_settings[id] = dict(
-                id=id,
-                schema=schema,
-                version=version,
-                **user_settings
-            )
-
-    settings.update(federated_settings)
-    settings_list = [settings[key] for key in sorted(settings.keys(), 
reverse=True)]
-
-    return (settings_list, warnings)
-
-
-def _override(schema_name, schema, overrides):
-    """Override default values in the schema if necessary."""
-    if schema_name in overrides:
-        defaults = overrides[schema_name]
-        for key in defaults:
-            if key in schema['properties']:
-                new_defaults = schema['properties'][key]['default']
-                # If values for defaults are dicts do a recursive update
-                if isinstance(new_defaults, dict):
-                    recursive_update(new_defaults, defaults[key])
-                else:
-                    new_defaults = defaults[key]
-
-                schema['properties'][key]['default'] = new_defaults
-            else:
-                schema['properties'][key] = dict(default=defaults[key])
-
-    return schema
-
-
-def _path(root_dir, schema_name, make_dirs=False, extension='.json'):
-    """
-    Returns the local file system path for a schema name in the given root
-    directory. This function can be used to filed user overrides in addition to
-    schema files. If the `make_dirs` flag is set to `True` it will create the
-    parent directory for the calculated path if it does not exist.
-    """
-
-    parent_dir = root_dir
-    notfound_error = 'Settings not found (%s)'
-    write_error = 'Failed writing settings (%s): %s'
-
-    try:  # to parse path, e.g. @jupyterlab/apputils-extension:themes.
-        package_dir, plugin = schema_name.split(':')
-        parent_dir = os.path.join(root_dir, package_dir)
-        path = os.path.join(parent_dir, plugin + extension)
-    except Exception:
-        raise web.HTTPError(404, notfound_error % schema_name)
-
-    if make_dirs and not os.path.exists(parent_dir):
-        try:
-            os.makedirs(parent_dir)
-        except Exception as e:
-            raise web.HTTPError(500, write_error % (schema_name, str(e)))
-
-    return path
-
-
-def _get_overrides(app_settings_dir):
-    """Get overrides settings from `app_settings_dir`."""
-    overrides, error = {}, ""
-    overrides_path = os.path.join(app_settings_dir, 'overrides.json')
-
-    if not os.path.exists(overrides_path):
-        overrides_path = os.path.join(app_settings_dir, 'overrides.json5')
-
-    if os.path.exists(overrides_path):
-        with open(overrides_path, encoding='utf-8') as fid:
-            try:
-                overrides = json5.load(fid)
-            except Exception as e:
-                error = e
-
-    # Allow `default_settings_overrides.json` files in 
<jupyter_config>/labconfig dirs
-    # to allow layering of defaults
-    cm = ConfigManager(config_dir_name="labconfig")
-    recursive_update(overrides, cm.get('default_setting_overrides'))
-
-    return overrides, error
-
-
-def get_settings(app_settings_dir, schemas_dir, settings_dir, schema_name="", 
overrides=None, labextensions_path=None):
-    """
-    Get setttings.
-
-    Parameters
-    ----------
-    app_settings_dir:
-        Path to applications settings.
-    schemas_dir: str
-        Path to schemas.
-    settings_dir:
-        Path to settings.
-    schema_name str, optional
-        Schema name. Default is "".
-    overrides: dict, optional
-        Settings overrides. If not provided, the overrides will be loaded
-        from the `app_settings_dir`. Default is None.
-    labextensions_path: list, optional
-        List of paths to federated labextensions containing their own schema 
files.
-
-    Returns
-    -------
-    tuple
-        The first item is a dictionary with a list of setting if no 
`schema_name`
-        was provided, otherwise it is a dictionary with id, raw, scheme, 
settings
-        and version keys. The second item is a list of warnings. Warnings will
-        either be a list of i) strings with the warning messages or ii) `None`.
-    """
-    result = {}
-    warnings = []
-
-    if overrides is None:
-        overrides, _error = _get_overrides(app_settings_dir)
-
-    if schema_name:
-        schema, version = _get_schema(schemas_dir, schema_name, overrides, 
labextensions_path)
-        user_settings = _get_user_settings(settings_dir, schema_name, schema)
-        warnings = [user_settings.pop('warning')]
-        result = {
-            "id": schema_name,
-            "schema": schema,
-            "version": version,
-            **user_settings
-        }
-    else:
-        settings_list, warnings = _list_settings(schemas_dir, settings_dir, 
overrides, labextensions_path=labextensions_path)
-        result = {
-            "settings": settings_list,
-        }
-
-    return result, warnings
-
-
-class SettingsHandler(ExtensionHandlerMixin, ExtensionHandlerJinjaMixin, 
APIHandler):
-
-    def initialize(self, name, app_settings_dir, schemas_dir, settings_dir, 
labextensions_path, **kwargs):
-        super().initialize(name)
-        self.overrides, error = _get_overrides(app_settings_dir)
-        self.app_settings_dir = app_settings_dir
-        self.schemas_dir = schemas_dir
-        self.settings_dir = settings_dir
-        self.labextensions_path = labextensions_path
-
-        if error:
-            overrides_warning = 'Failed loading overrides: %s'
-            self.log.warn(overrides_warning % str(error))
+        ExtensionHandlerMixin.initialize(self, name)
 
     @web.authenticated
     def get(self, schema_name=""):
         """Get setting(s)"""
+        # Need to be update here as translator locale is not change when a new 
locale is put
+        # from frontend
+        try:
+            locale = self.get_current_locale()
+        except web.HTTPError as e:
+            # fallback in case of missing (404) or misshapen (500) translation 
schema
+            locale = DEFAULT_LOCALE
+            'Failed loading or validating translation settings schema'
+
+        translator.set_locale(locale)
+
         result, warnings = get_settings(
             self.app_settings_dir,
             self.schemas_dir,
@@ -359,6 +51,7 @@
             labextensions_path=self.labextensions_path,
             schema_name=schema_name,
             overrides=self.overrides,
+            translator=translator.translate_schema
         )
 
         # Print all warnings.
@@ -384,24 +77,20 @@
 
         raw_payload = self.request.body.strip().decode('utf-8')
         try:
-            raw_settings = json.loads(raw_payload)['raw']
-            payload = json5.loads(raw_settings)
+            raw_settings = json.loads(raw_payload)["raw"]
+            save_settings(
+                schemas_dir,
+                settings_dir,
+                schema_name,
+                raw_settings,
+                overrides,
+                self.labextensions_path,
+            )
         except json.decoder.JSONDecodeError as e:
             raise web.HTTPError(400, invalid_json_error % str(e))
         except (KeyError, TypeError) as e:
             raise web.HTTPError(400, invalid_payload_format_error)
-
-        # Validate the data against the schema.
-        schema, _ = _get_schema(schemas_dir, schema_name, overrides, 
labextensions_path=self.labextensions_path)
-        validator = Validator(schema)
-        try:
-            validator.validate(payload)
         except ValidationError as e:
             raise web.HTTPError(400, validation_error % str(e))
 
-        # Write the raw data (comments included) to a file.
-        path = _path(settings_dir, schema_name, True, SETTINGS_EXTENSION)
-        with open(path, 'w', encoding='utf-8') as fid:
-            fid.write(raw_settings)
-
         self.set_status(204)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyterlab_server-2.7.0/jupyterlab_server/settings_utils.py 
new/jupyterlab_server-2.8.1/jupyterlab_server/settings_utils.py
--- old/jupyterlab_server-2.7.0/jupyterlab_server/settings_utils.py     
1970-01-01 01:00:00.000000000 +0100
+++ new/jupyterlab_server-2.8.1/jupyterlab_server/settings_utils.py     
2021-09-07 20:22:44.000000000 +0200
@@ -0,0 +1,456 @@
+"""Frontend config storage helpers."""
+
+# Copyright (c) Jupyter Development Team.
+# Distributed under the terms of the Modified BSD License.
+from glob import glob
+import json
+import json5
+from jsonschema import Draft4Validator as Validator, ValidationError
+from jupyter_server.services.config.manager import ConfigManager, 
recursive_update
+import os
+from tornado import web
+
+from .server import APIHandler, tz
+from .translation_utils import DEFAULT_LOCALE, L10N_SCHEMA_NAME, 
is_valid_locale
+
+# The JupyterLab settings file extension.
+SETTINGS_EXTENSION = '.jupyterlab-settings'
+
+
+def _get_schema(schemas_dir, schema_name, overrides, labextensions_path):
+    """Returns a dict containing a parsed and validated JSON schema."""
+    notfound_error = 'Schema not found: %s'
+    parse_error = 'Failed parsing schema (%s): %s'
+    validation_error = 'Failed validating schema (%s): %s'
+
+    path = None
+
+    # Look for the setting in all of the labextension paths first
+    # Use the first one
+    if labextensions_path is not None:
+        ext_name, _, plugin_name = schema_name.partition(':')
+        for ext_path in labextensions_path:
+            target = os.path.join(ext_path, ext_name, 'schemas', ext_name, 
plugin_name + '.json')
+            if os.path.exists(target):
+                schemas_dir = os.path.join(ext_path, ext_name, 'schemas')
+                path = target
+                break
+
+    # Fall back on the default location
+    if path is None:
+        path = _path(schemas_dir, schema_name)
+
+    if not os.path.exists(path):
+        raise web.HTTPError(404, notfound_error % path)
+
+    with open(path, encoding='utf-8') as fid:
+        # Attempt to load the schema file.
+        try:
+            schema = json.load(fid)
+        except Exception as e:
+            name = schema_name
+            raise web.HTTPError(500, parse_error % (name, str(e)))
+
+    schema = _override(schema_name, schema, overrides)
+
+    # Validate the schema.
+    try:
+        Validator.check_schema(schema)
+    except Exception as e:
+        name = schema_name
+        raise web.HTTPError(500, validation_error % (name, str(e)))
+
+    version = _get_version(schemas_dir, schema_name)
+
+    return schema, version
+
+
+def _get_user_settings(settings_dir, schema_name, schema):
+    """
+    Returns a dictionary containing the raw user settings, the parsed user
+    settings, a validation warning for a schema, and file times.
+    """
+    path = _path(settings_dir, schema_name, False, SETTINGS_EXTENSION)
+    raw = '{}'
+    settings = {}
+    warning = None
+    validation_warning = 'Failed validating settings (%s): %s'
+    parse_error = 'Failed loading settings (%s): %s'
+    last_modified = None
+    created = None
+
+    if os.path.exists(path):
+        stat = os.stat(path)
+        last_modified = tz.utcfromtimestamp(stat.st_mtime).isoformat()
+        created = tz.utcfromtimestamp(stat.st_ctime).isoformat()
+        with open(path, encoding='utf-8') as fid:
+            try:  # to load and parse the settings file.
+                raw = fid.read() or raw
+                settings = json5.loads(raw)
+            except Exception as e:
+                raise web.HTTPError(500, parse_error % (schema_name, str(e)))
+
+    # Validate the parsed data against the schema.
+    if len(settings):
+        validator = Validator(schema)
+        try:
+            validator.validate(settings)
+        except ValidationError as e:
+            warning = validation_warning % (schema_name, str(e))
+            raw = '{}'
+            settings = {}
+
+    return dict(
+        raw=raw,
+        settings=settings,
+        warning=warning,
+        last_modified=last_modified,
+        created=created
+    )
+
+
+def _get_version(schemas_dir, schema_name):
+    """Returns the package version for a given schema or 'N/A' if not found."""
+
+    path = _path(schemas_dir, schema_name)
+    package_path = os.path.join(os.path.split(path)[0], 'package.json.orig')
+
+    try:  # to load and parse the package.json.orig file.
+        with open(package_path, encoding='utf-8') as fid:
+            package = json.load(fid)
+            return package['version']
+    except Exception:
+        return 'N/A'
+
+
+def _list_settings(
+    schemas_dir,
+    settings_dir,
+    overrides,
+    extension=".json",
+    labextensions_path=None,
+    translator=None,
+):
+    """
+    Returns a tuple containing:
+     - the list of plugins, schemas, and their settings,
+       respecting any defaults that may have been overridden.
+     - the list of warnings that were generated when
+       validating the user overrides against the schemas.
+    """
+
+    settings = {}
+    federated_settings = {}
+    warnings = []
+
+    if not os.path.exists(schemas_dir):
+        warnings = ['Settings directory does not exist at %s' % schemas_dir]
+        return ([], warnings)
+
+    schema_pattern = schemas_dir + '/**/*' + extension
+    schema_paths = [path for path in glob(schema_pattern, recursive=True)]
+    schema_paths.sort()
+
+    for schema_path in schema_paths:
+        # Generate the schema_name used to request individual settings.
+        rel_path = os.path.relpath(schema_path, schemas_dir)
+        rel_schema_dir, schema_base = os.path.split(rel_path)
+        id = schema_name = ':'.join([
+            rel_schema_dir,
+            schema_base[:-len(extension)]  # Remove file extension.
+        ]).replace('\\', '/')               # Normalize slashes.
+        schema, version = _get_schema(schemas_dir, schema_name, overrides, 
None)
+        if translator is not None:
+            schema = translator(schema)
+        user_settings = _get_user_settings(settings_dir, schema_name, schema)
+
+        if user_settings["warning"]:
+            warnings.append(user_settings.pop('warning'))
+
+        # Add the plugin to the list of settings.
+        settings[id] = dict(
+            id=id,
+            schema=schema,
+            version=version,
+            **user_settings
+        )
+
+    if labextensions_path is not None:
+        schema_paths = []
+        for ext_dir in labextensions_path:
+            schema_pattern = ext_dir + '/**/schemas/**/*' + extension
+            schema_paths.extend([path for path in glob(schema_pattern, 
recursive=True)])
+
+        schema_paths.sort()
+
+        for schema_path in schema_paths:
+            schema_path = schema_path.replace(os.sep, '/')
+
+            base_dir, rel_path = schema_path.split('schemas/')
+
+            # Generate the schema_name used to request individual settings.
+            rel_schema_dir, schema_base = os.path.split(rel_path)
+            id = schema_name = ':'.join([
+                rel_schema_dir,
+                schema_base[:-len(extension)]  # Remove file extension.
+            ]).replace('\\', '/')               # Normalize slashes.
+
+            # bail if we've already handled the highest federated setting
+            if id in federated_settings:
+                continue
+
+            schema, version = _get_schema(schemas_dir, schema_name, overrides, 
labextensions_path=labextensions_path)
+            user_settings = _get_user_settings(settings_dir, schema_name, 
schema)
+
+            if user_settings["warning"]:
+                warnings.append(user_settings.pop('warning'))
+
+            # Add the plugin to the list of settings.
+            federated_settings[id] = dict(
+                id=id,
+                schema=schema,
+                version=version,
+                **user_settings
+            )
+
+    settings.update(federated_settings)
+    settings_list = [settings[key] for key in sorted(settings.keys(), 
reverse=True)]
+
+    return (settings_list, warnings)
+
+
+def _override(schema_name, schema, overrides):
+    """Override default values in the schema if necessary."""
+    if schema_name in overrides:
+        defaults = overrides[schema_name]
+        for key in defaults:
+            if key in schema['properties']:
+                new_defaults = schema['properties'][key]['default']
+                # If values for defaults are dicts do a recursive update
+                if isinstance(new_defaults, dict):
+                    recursive_update(new_defaults, defaults[key])
+                else:
+                    new_defaults = defaults[key]
+
+                schema['properties'][key]['default'] = new_defaults
+            else:
+                schema['properties'][key] = dict(default=defaults[key])
+
+    return schema
+
+
+def _path(root_dir, schema_name, make_dirs=False, extension='.json'):
+    """
+    Returns the local file system path for a schema name in the given root
+    directory. This function can be used to filed user overrides in addition to
+    schema files. If the `make_dirs` flag is set to `True` it will create the
+    parent directory for the calculated path if it does not exist.
+    """
+
+    parent_dir = root_dir
+    notfound_error = 'Settings not found (%s)'
+    write_error = 'Failed writing settings (%s): %s'
+
+    try:  # to parse path, e.g. @jupyterlab/apputils-extension:themes.
+        package_dir, plugin = schema_name.split(':')
+        parent_dir = os.path.join(root_dir, package_dir)
+        path = os.path.join(parent_dir, plugin + extension)
+    except Exception:
+        raise web.HTTPError(404, notfound_error % schema_name)
+
+    if make_dirs and not os.path.exists(parent_dir):
+        try:
+            os.makedirs(parent_dir)
+        except Exception as e:
+            raise web.HTTPError(500, write_error % (schema_name, str(e)))
+
+    return path
+
+
+def _get_overrides(app_settings_dir):
+    """Get overrides settings from `app_settings_dir`."""
+    overrides, error = {}, ""
+    overrides_path = os.path.join(app_settings_dir, 'overrides.json')
+
+    if not os.path.exists(overrides_path):
+        overrides_path = os.path.join(app_settings_dir, 'overrides.json5')
+
+    if os.path.exists(overrides_path):
+        with open(overrides_path, encoding='utf-8') as fid:
+            try:
+                overrides = json5.load(fid)
+            except Exception as e:
+                error = e
+
+    # Allow `default_settings_overrides.json` files in 
<jupyter_config>/labconfig dirs
+    # to allow layering of defaults
+    cm = ConfigManager(config_dir_name="labconfig")
+    recursive_update(overrides, cm.get('default_setting_overrides'))
+
+    return overrides, error
+
+
+def get_settings(
+    app_settings_dir,
+    schemas_dir,
+    settings_dir,
+    schema_name="",
+    overrides=None,
+    labextensions_path=None,
+    translator=None,
+):
+    """
+    Get settings.
+
+    Parameters
+    ----------
+    app_settings_dir:
+        Path to applications settings.
+    schemas_dir: str
+        Path to schemas.
+    settings_dir:
+        Path to settings.
+    schema_name str, optional
+        Schema name. Default is "".
+    overrides: dict, optional
+        Settings overrides. If not provided, the overrides will be loaded
+        from the `app_settings_dir`. Default is None.
+    labextensions_path: list, optional
+        List of paths to federated labextensions containing their own schema 
files.
+    translator: Callable[[Dict], Dict] or None, optional
+        Translate a schema. It requires the schema dictionary and returns its 
translation
+
+    Returns
+    -------
+    tuple
+        The first item is a dictionary with a list of setting if no 
`schema_name`
+        was provided, otherwise it is a dictionary with id, raw, scheme, 
settings
+        and version keys. The second item is a list of warnings. Warnings will
+        either be a list of i) strings with the warning messages or ii) `None`.
+    """
+    result = {}
+    warnings = []
+
+    if overrides is None:
+        overrides, _error = _get_overrides(app_settings_dir)
+
+    if schema_name:
+        schema, version = _get_schema(
+            schemas_dir, schema_name, overrides, labextensions_path
+        )
+        if translator is not None:
+            schema = translator(schema)
+        user_settings = _get_user_settings(settings_dir, schema_name, schema)
+        warnings = [user_settings.pop('warning')]
+        result = {
+            "id": schema_name,
+            "schema": schema,
+            "version": version,
+            **user_settings
+        }
+    else:
+        settings_list, warnings = _list_settings(
+            schemas_dir,
+            settings_dir,
+            overrides,
+            labextensions_path=labextensions_path,
+            translator=translator,
+        )
+        result = {
+            "settings": settings_list,
+        }
+
+    return result, warnings
+
+
+def save_settings(
+    schemas_dir,
+    settings_dir,
+    schema_name,
+    raw_settings,
+    overrides,
+    labextensions_path=None,
+):
+    """
+    Save ``raw_settings`` settings for ``schema_name``.
+
+    Parameters
+    ----------
+    schemas_dir: str
+        Path to schemas.
+    settings_dir: str
+        Path to settings.
+    schema_name str
+        Schema name.
+    raw_settings: str
+        Raw serialized settings dictionary
+    overrides: dict
+        Settings overrides.
+    labextensions_path: list, optional
+        List of paths to federated labextensions containing their own schema 
files.
+    """
+    payload = json5.loads(raw_settings)
+
+    # Validate the data against the schema.
+    schema, _ = _get_schema(
+        schemas_dir, schema_name, overrides, 
labextensions_path=labextensions_path
+    )
+    validator = Validator(schema)
+    validator.validate(payload)
+
+    # Write the raw data (comments included) to a file.
+    path = _path(settings_dir, schema_name, True, SETTINGS_EXTENSION)
+    with open(path, "w", encoding="utf-8") as fid:
+        fid.write(raw_settings)
+
+
+class SchemaHandler(APIHandler):
+    """Base handler for handler requiring access to settings."""
+
+    def initialize(
+        self, app_settings_dir, schemas_dir, settings_dir, labextensions_path, 
**kwargs
+    ):
+        super().initialize(**kwargs)
+        self.overrides, error = _get_overrides(app_settings_dir)
+        self.app_settings_dir = app_settings_dir
+        self.schemas_dir = schemas_dir
+        self.settings_dir = settings_dir
+        self.labextensions_path = labextensions_path
+
+        if error:
+            overrides_warning = "Failed loading overrides: %s"
+            self.log.warn(overrides_warning % str(error))
+
+    def get_current_locale(self):
+        """
+        Get the current locale as specified in the translation-extension 
settings.
+
+        Returns
+        -------
+        str
+            The current locale string.
+
+        Notes
+        -----
+        If the locale setting is not available or not valid, it will default 
to jupyterlab_server.translation_utils.DEFAULT_LOCALE.
+        """
+        try:
+            settings, _ = get_settings(
+                self.app_settings_dir,
+                self.schemas_dir,
+                self.settings_dir,
+                schema_name=L10N_SCHEMA_NAME,
+                overrides=self.overrides,
+                labextensions_path=self.labextensions_path,
+            )
+        except web.HTTPError as e:
+            schema_warning = "Missing or misshappen translation settings 
schema:\n%s"
+            self.log.warn(schema_warning % str(e))
+
+            settings = {}
+
+        current_locale = settings.get("settings", {}).get("locale", 
DEFAULT_LOCALE)
+        if not is_valid_locale(current_locale):
+            current_locale = DEFAULT_LOCALE
+
+        return current_locale
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyterlab_server-2.7.0/jupyterlab_server/tests/test_translation_api.py 
new/jupyterlab_server-2.8.1/jupyterlab_server/tests/test_translation_api.py
--- old/jupyterlab_server-2.7.0/jupyterlab_server/tests/test_translation_api.py 
2021-08-11 16:05:01.000000000 +0200
+++ new/jupyterlab_server-2.8.1/jupyterlab_server/tests/test_translation_api.py 
2021-09-07 20:22:44.000000000 +0200
@@ -219,6 +219,7 @@
     assert get_display_name("en", "fr") == "Anglais"
     assert get_display_name("es", "en") == "Spanish"
     assert get_display_name("fr", "en") == "French"
+    assert get_display_name("pl_pl", "en") == "Polish (Poland)"
 
 
 def test_get_display_name_invalid():
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyterlab_server-2.7.0/jupyterlab_server/tests/utils.py 
new/jupyterlab_server-2.8.1/jupyterlab_server/tests/utils.py
--- old/jupyterlab_server-2.7.0/jupyterlab_server/tests/utils.py        
2021-08-11 16:05:01.000000000 +0200
+++ new/jupyterlab_server-2.8.1/jupyterlab_server/tests/utils.py        
2021-09-07 20:22:44.000000000 +0200
@@ -44,7 +44,7 @@
     # work around lack of support for path parameters which can contain slashes
     # https://github.com/OAI/OpenAPI-Specification/issues/892
     url = None
-    for path in spec.paths:
+    for path in spec['paths']:
         if url:
             continue
         has_arg = '{' in path
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyterlab_server-2.7.0/jupyterlab_server/translation_utils.py 
new/jupyterlab_server-2.8.1/jupyterlab_server/translation_utils.py
--- old/jupyterlab_server-2.7.0/jupyterlab_server/translation_utils.py  
2021-08-11 16:05:01.000000000 +0200
+++ new/jupyterlab_server-2.8.1/jupyterlab_server/translation_utils.py  
2021-09-07 20:22:44.000000000 +0200
@@ -7,9 +7,11 @@
 import importlib
 import json
 import os
-import subprocess
+import re
 import sys
 import traceback
+from functools import lru_cache
+from typing import Dict, Pattern
 
 import babel
 import entrypoints
@@ -23,6 +25,44 @@
 DEFAULT_LOCALE = "en"
 LOCALE_DIR = "locale"
 LC_MESSAGES_DIR = "LC_MESSAGES"
+DEFAULT_DOMAIN = "jupyterlab"
+L10N_SCHEMA_NAME = "@jupyterlab/translation-extension:plugin"
+
+_default_schema_context = "schema"
+_default_settings_context = "settings"
+_lab_i18n_config = "jupyter.lab.internationalization"
+
+# mapping of schema translatable string selectors to translation context
+DEFAULT_SCHEMA_SELECTORS = {
+    "properties/.*/title": _default_settings_context,
+    "properties/.*/description": _default_settings_context,
+    "definitions/.*/properties/.*/title": _default_settings_context,
+    "definitions/.*/properties/.*/description": _default_settings_context,
+    "title": _default_schema_context,
+    "description": _default_schema_context,
+    # JupyterLab-specific
+    "jupyter\.lab\.setting-icon-label": _default_settings_context,
+    "jupyter\.lab\.menus/.*/label": "menu",
+    "jupyter\.lab\.toolbars/.*/label": "toolbar",
+}
+
+
+@lru_cache()
+def _get_default_schema_selectors() -> Dict[Pattern, str]:
+    return {
+        re.compile("^/" + pattern + "$"): context
+        for pattern, context in DEFAULT_SCHEMA_SELECTORS.items()
+    }
+
+
+def _prepare_schema_patterns(schema: dict) -> Dict[Pattern, str]:
+    return {
+        **_get_default_schema_selectors(),
+        **{
+            re.compile("^/" + selector + "$"): _default_schema_context
+            for selector in schema.get(_lab_i18n_config, {}).get("selectors", 
[])
+        },
+    }
 
 
 # --- Private process helpers
@@ -164,7 +204,10 @@
         display_locale if is_valid_locale(display_locale) else DEFAULT_LOCALE
     )
     loc = babel.Locale.parse(locale)
-    return loc.get_display_name(display_locale).capitalize()
+    display_name = loc.get_display_name(display_locale)
+    if display_name:
+        display_name = display_name[0].upper() + display_name[1:]
+    return display_name
 
 
 def merge_locale_data(language_pack_locale_data, package_locale_data):
@@ -428,7 +471,7 @@
         """
         return gettext.dngettext(self._domain, msgid, msgid_plural, n)
 
-    def pgettext(self, msgctxt: str, singular: str) -> str:
+    def pgettext(self, msgctxt: str, msgid: str) -> str:
         """
         Translate a singular string with context.
 
@@ -503,7 +546,7 @@
         str
             The translated string.
         """
-        return self.ngettext(msgid, plural, n)
+        return self.ngettext(msgid, msgid_plural, n)
 
     def _p(self, msgctxt: str, msgid: str) -> str:
         """
@@ -523,7 +566,7 @@
         """
         return self.pgettext(msgctxt, msgid)
 
-    def _np(self, msgctxt: str, msgid: str, msgid_plular: str, n: str) -> str:
+    def _np(self, msgctxt: str, msgid: str, msgid_plural: str, n: str) -> str:
         """
         Shorthand for npgettext.
 
@@ -543,13 +586,14 @@
         str
             The translated string.
         """
-        return self.npgettext(msgctxt, msgid, msgid_plular, n)
+        return self.npgettext(msgctxt, msgid, msgid_plural, n)
 
 
 class translator:
     """
     Translations manager.
     """
+
     _TRANSLATORS = {}
     _LOCALE = DEFAULT_LOCALE
 
@@ -566,6 +610,22 @@
         for key in ["LANGUAGE", "LANG"]:
             os.environ[key] = f"{locale}.UTF-8"
 
+    @staticmethod
+    def normalize_domain(domain: str) -> str:
+        """Normalize a domain name.
+
+        Parameters
+        ----------
+        domain: str
+            Domain to normalize
+
+        Returns
+        -------
+        str
+            Normalized domain
+        """
+        return domain.replace("-", "_")
+
     @classmethod
     def set_locale(cls, locale: str):
         """
@@ -576,6 +636,10 @@
         locale: str
             The language name to use.
         """
+        if locale == cls._LOCALE:
+            # Nothing to do bail early
+            return
+
         if is_valid_locale(locale):
             cls._LOCALE = locale
             translator._update_env(locale)
@@ -599,14 +663,81 @@
         Translator
             A translator instance bound to the domain.
         """
-        if domain in cls._TRANSLATORS:
-            trans = cls._TRANSLATORS[domain]
+        norm_domain = translator.normalize_domain(domain)
+        if norm_domain in cls._TRANSLATORS:
+            trans = cls._TRANSLATORS[norm_domain]
         else:
-            trans = TranslationBundle(domain, cls._LOCALE)
-            cls._TRANSLATORS[domain] = trans
+            trans = TranslationBundle(norm_domain, cls._LOCALE)
+            cls._TRANSLATORS[norm_domain] = trans
 
         return trans
 
+    @staticmethod
+    def _translate_schema_strings(
+        translations,
+        schema: dict,
+        prefix: str = "",
+        to_translate: Dict[Pattern, str] = None,
+    ) -> None:
+        """Translate a schema in-place."""
+        if to_translate is None:
+            to_translate = _prepare_schema_patterns(schema)
+
+        for key, value in schema.items():
+            path = prefix + "/" + key
+
+            if isinstance(value, str):
+                matched = False
+                for pattern, context in to_translate.items():
+                    if pattern.fullmatch(path):
+                        matched = True
+                        break
+                if matched:
+                    schema[key] = translations.pgettext(context, value)
+            elif isinstance(value, dict):
+                translator._translate_schema_strings(
+                    translations,
+                    value,
+                    prefix=path,
+                    to_translate=to_translate,
+                )
+            elif isinstance(value, list):
+                for i, element in enumerate(value):
+                    if not isinstance(element, dict):
+                        continue
+                    translator._translate_schema_strings(
+                        translations,
+                        element,
+                        prefix=path + "[" + str(i) + "]",
+                        to_translate=to_translate,
+                    )
+
+    @staticmethod
+    def translate_schema(schema: Dict) -> Dict:
+        """Translate a schema.
+        
+        Parameters
+        ----------
+        schema: dict
+            The schema to be translated
+
+        Returns
+        -------
+        Dict
+            The translated schema
+        """
+        if translator._LOCALE == DEFAULT_LOCALE:
+            return schema
+
+        translations = translator.load(
+            schema.get(_lab_i18n_config, {}).get("domain", DEFAULT_DOMAIN)
+        )
+
+        new_schema = schema.copy()
+        translator._translate_schema_strings(translations, schema.copy())
+
+        return new_schema
+
 
 if __name__ == "__main__":
     _main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyterlab_server-2.7.0/jupyterlab_server/translations_handler.py 
new/jupyterlab_server-2.8.1/jupyterlab_server/translations_handler.py
--- old/jupyterlab_server-2.7.0/jupyterlab_server/translations_handler.py       
2021-08-11 16:05:01.000000000 +0200
+++ new/jupyterlab_server-2.8.1/jupyterlab_server/translations_handler.py       
2021-09-07 20:22:44.000000000 +0200
@@ -11,49 +11,11 @@
 import tornado
 from tornado import gen
 
-from .server import APIHandler
-from .settings_handler import get_settings
+from .settings_utils import SchemaHandler
 from .translation_utils import get_language_pack, get_language_packs, 
is_valid_locale, translator
 
 
-SCHEMA_NAME = '@jupyterlab/translation-extension:plugin'
-
-
-def get_current_locale(config):
-    """
-    Get the current locale for given `config`.
-
-    Parameters
-    ----------
-    config: LabConfig
-        The config.
-
-    Returns
-    -------
-    str
-        The current locale string.
-
-    Notes
-    -----
-    If the locale setting is not valid, it will default to "en".
-    """
-    settings, _warnings = get_settings(
-        config.app_settings_dir,
-        config.schemas_dir,
-        config.user_settings_dir,
-        schema_name=SCHEMA_NAME,
-    )
-    current_locale = settings.get("settings", {}).get("locale", "en")
-    if not is_valid_locale(current_locale):
-        current_locale = "en"
-
-    return current_locale
-
-
-class TranslationsHandler(APIHandler):
-
-    def initialize(self, lab_config):
-        self.lab_config = lab_config
+class TranslationsHandler(SchemaHandler):
 
     @gen.coroutine
     @tornado.web.authenticated
@@ -71,7 +33,8 @@
         try:
             if locale == "":
                 data, message = get_language_packs(
-                    display_locale=get_current_locale(self.lab_config))
+                    display_locale=self.get_current_locale()
+                )
             else:
                 data, message = get_language_pack(locale)
                 if data == {} and message == "":
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyterlab_server-2.7.0/jupyterlab_server.egg-info/PKG-INFO 
new/jupyterlab_server-2.8.1/jupyterlab_server.egg-info/PKG-INFO
--- old/jupyterlab_server-2.7.0/jupyterlab_server.egg-info/PKG-INFO     
2021-08-11 16:05:38.000000000 +0200
+++ new/jupyterlab_server-2.8.1/jupyterlab_server.egg-info/PKG-INFO     
2021-09-07 20:23:32.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: jupyterlab-server
-Version: 2.7.0
+Version: 2.8.1
 Summary: A set of server components for JupyterLab and JupyterLab like 
applications .
 Home-page: https://jupyter.org
 Author: Jupyter Development Team
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyterlab_server-2.7.0/jupyterlab_server.egg-info/SOURCES.txt 
new/jupyterlab_server-2.8.1/jupyterlab_server.egg-info/SOURCES.txt
--- old/jupyterlab_server-2.7.0/jupyterlab_server.egg-info/SOURCES.txt  
2021-08-11 16:05:38.000000000 +0200
+++ new/jupyterlab_server-2.8.1/jupyterlab_server.egg-info/SOURCES.txt  
2021-09-07 20:23:32.000000000 +0200
@@ -34,6 +34,7 @@
 jupyterlab_server/rest-api.yml
 jupyterlab_server/server.py
 jupyterlab_server/settings_handler.py
+jupyterlab_server/settings_utils.py
 jupyterlab_server/themes_handler.py
 jupyterlab_server/translation_utils.py
 jupyterlab_server/translations_handler.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/jupyterlab_server-2.7.0/jupyterlab_server.egg-info/requires.txt 
new/jupyterlab_server-2.8.1/jupyterlab_server.egg-info/requires.txt
--- old/jupyterlab_server-2.7.0/jupyterlab_server.egg-info/requires.txt 
2021-08-11 16:05:38.000000000 +0200
+++ new/jupyterlab_server-2.8.1/jupyterlab_server.egg-info/requires.txt 
2021-09-07 20:23:32.000000000 +0200
@@ -13,7 +13,7 @@
 pytest>=5.3.2
 pytest-cov
 jupyter_server[test]
-openapi_core~=0.13.8
+openapi_core~=0.14.0
 pytest-console-scripts
 strict-rfc3339
 ruamel.yaml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyterlab_server-2.7.0/pyproject.toml 
new/jupyterlab_server-2.8.1/pyproject.toml
--- old/jupyterlab_server-2.7.0/pyproject.toml  2021-08-11 16:05:24.000000000 
+0200
+++ new/jupyterlab_server-2.8.1/pyproject.toml  2021-09-07 20:23:14.000000000 
+0200
@@ -2,12 +2,15 @@
 requires = ["jupyter_packaging~=0.9,<2", "jupyter_server"]
 build-backend = "setuptools.build_meta"
 
+[tool.jupyter-releaser]
+skip = ["check-links"]
+
 [tool.check-manifest]
 ignore = ["tbump.toml", ".*", "*.yml", "docs/source/api/app-config.rst", 
"docs/source/changelog.md"]
 ignore-bad-ideas = ["jupyterlab_server/tests/translations/**/*.mo"]
 
 [tool.tbump.version]
-current = "2.7.0"
+current = "2.8.1"
 regex = '''
   (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
   ((?P<channel>a|b|rc|.dev)(?P<release>\d+))?
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/jupyterlab_server-2.7.0/setup.cfg 
new/jupyterlab_server-2.8.1/setup.cfg
--- old/jupyterlab_server-2.7.0/setup.cfg       2021-08-11 16:05:38.722334000 
+0200
+++ new/jupyterlab_server-2.8.1/setup.cfg       2021-09-07 20:23:32.172667000 
+0200
@@ -37,7 +37,7 @@
        jupyter_server~=1.4
 
 [options.extras_require]
-test = codecov; ipykernel; pytest>=5.3.2; pytest-cov; jupyter_server[test]; 
openapi_core~=0.13.8; pytest-console-scripts; strict-rfc3339; ruamel.yaml; wheel
+test = codecov; ipykernel; pytest>=5.3.2; pytest-cov; jupyter_server[test]; 
openapi_core~=0.14.0; pytest-console-scripts; strict-rfc3339; ruamel.yaml; wheel
 
 [options.packages.find]
 exclude = ['docs*']

Reply via email to