oslo.i18n-4.0.1/0000775000175000017500000000000013643050342013270 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo.i18n.egg-info/0000775000175000017500000000000013643050342016514 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo.i18n.egg-info/top_level.txt0000664000175000017500000000001213643050342021237 0ustar zuulzuul00000000000000oslo_i18n oslo.i18n-4.0.1/oslo.i18n.egg-info/SOURCES.txt0000664000175000017500000000457413643050342020412 0ustar zuulzuul00000000000000.coveragerc .mailmap .stestr.conf .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst babel.cfg lower-constraints.txt requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/requirements.txt doc/source/conf.py doc/source/index.rst doc/source/contributor/contributing.rst doc/source/contributor/index.rst doc/source/contributor/policy.rst doc/source/reference/index.rst doc/source/user/guidelines.rst doc/source/user/history.rst doc/source/user/index.rst doc/source/user/usage.rst oslo.i18n.egg-info/PKG-INFO oslo.i18n.egg-info/SOURCES.txt oslo.i18n.egg-info/dependency_links.txt oslo.i18n.egg-info/not-zip-safe oslo.i18n.egg-info/pbr.json oslo.i18n.egg-info/requires.txt oslo.i18n.egg-info/top_level.txt oslo_i18n/__init__.py oslo_i18n/_factory.py oslo_i18n/_gettextutils.py oslo_i18n/_i18n.py oslo_i18n/_lazy.py oslo_i18n/_locale.py oslo_i18n/_message.py oslo_i18n/_translate.py oslo_i18n/fixture.py oslo_i18n/log.py oslo_i18n/locale/de/LC_MESSAGES/oslo_i18n.po oslo_i18n/locale/en_GB/LC_MESSAGES/oslo_i18n.po oslo_i18n/locale/es/LC_MESSAGES/oslo_i18n.po oslo_i18n/locale/fr/LC_MESSAGES/oslo_i18n.po oslo_i18n/locale/it/LC_MESSAGES/oslo_i18n.po oslo_i18n/locale/ja/LC_MESSAGES/oslo_i18n.po oslo_i18n/locale/ko_KR/LC_MESSAGES/oslo_i18n.po oslo_i18n/locale/pl_PL/LC_MESSAGES/oslo_i18n.po oslo_i18n/locale/pt/LC_MESSAGES/oslo_i18n.po oslo_i18n/locale/zh_CN/LC_MESSAGES/oslo_i18n.po oslo_i18n/tests/__init__.py oslo_i18n/tests/fakes.py oslo_i18n/tests/test_factory.py oslo_i18n/tests/test_fixture.py oslo_i18n/tests/test_gettextutils.py oslo_i18n/tests/test_handler.py oslo_i18n/tests/test_lazy.py oslo_i18n/tests/test_locale_dir_variable.py oslo_i18n/tests/test_logging.py oslo_i18n/tests/test_message.py oslo_i18n/tests/test_public_api.py oslo_i18n/tests/test_translate.py oslo_i18n/tests/utils.py releasenotes/notes/add-reno-996dd44974d53238.yaml releasenotes/notes/drop-python27-support-eb9c2ad3268f0c1f.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/queens.rst releasenotes/source/rocky.rst releasenotes/source/stein.rst releasenotes/source/train.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po releasenotes/source/locale/ko_KR/LC_MESSAGES/releasenotes.pooslo.i18n-4.0.1/oslo.i18n.egg-info/not-zip-safe0000664000175000017500000000000113643050342020742 0ustar zuulzuul00000000000000 oslo.i18n-4.0.1/oslo.i18n.egg-info/PKG-INFO0000664000175000017500000000422013643050342017607 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: oslo.i18n Version: 4.0.1 Summary: Oslo i18n library Home-page: https://docs.openstack.org/oslo.i18n/latest Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/oslo.i18n.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ================================================== oslo.i18n -- Oslo Internationalization Utilities ================================================== .. image:: https://img.shields.io/pypi/v/oslo.i18n.svg :target: https://pypi.org/project/oslo.i18n/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/oslo.i18n.svg :target: https://pypi.org/project/oslo.i18n/ :alt: Downloads The oslo.i18n library contain utilities for working with internationalization (i18n) features, especially translation for text strings in an application or library. * Free software: Apache license * Documentation: https://docs.openstack.org/oslo.i18n/latest * Source: https://opendev.org/openstack/oslo.i18n * Bugs: https://bugs.launchpad.net/oslo.i18n * Release notes: https://docs.openstack.org/releasenotes/oslo.i18n/ Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.6 oslo.i18n-4.0.1/oslo.i18n.egg-info/dependency_links.txt0000664000175000017500000000000113643050342022562 0ustar zuulzuul00000000000000 oslo.i18n-4.0.1/oslo.i18n.egg-info/requires.txt0000664000175000017500000000006413643050342021114 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 Babel!=2.4.0,>=2.3.4 six>=1.10.0 oslo.i18n-4.0.1/oslo.i18n.egg-info/pbr.json0000664000175000017500000000005613643050342020173 0ustar zuulzuul00000000000000{"git_version": "34aaaf2", "is_release": true}oslo.i18n-4.0.1/.zuul.yaml0000664000175000017500000000045013643050246015233 0ustar zuulzuul00000000000000- project: templates: - check-requirements - lib-forward-testing-python3 - openstack-cover-jobs - openstack-lower-constraints-jobs - openstack-python3-ussuri-jobs - periodic-stable-jobs - publish-openstack-docs-pti - release-notes-jobs-python3 oslo.i18n-4.0.1/requirements.txt0000664000175000017500000000044013643050246016555 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. pbr!=2.1.0,>=2.0.0 # Apache-2.0 Babel!=2.4.0,>=2.3.4 # BSD six>=1.10.0 # MIT oslo.i18n-4.0.1/setup.py0000664000175000017500000000127113643050246015006 0ustar zuulzuul00000000000000# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import setuptools setuptools.setup( setup_requires=['pbr>=2.0.0'], pbr=True) oslo.i18n-4.0.1/babel.cfg0000664000175000017500000000002013643050246015011 0ustar zuulzuul00000000000000[python: **.py] oslo.i18n-4.0.1/LICENSE0000664000175000017500000002363613643050246014312 0ustar zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. oslo.i18n-4.0.1/oslo_i18n/0000775000175000017500000000000013643050342015103 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/_gettextutils.py0000664000175000017500000001012213643050246020360 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """gettextutils provides a wrapper around gettext for OpenStack projects """ import copy import gettext import os from babel import localedata from oslo_i18n import _factory from oslo_i18n import _locale __all__ = [ 'install', 'get_available_languages', ] def install(domain): """Install a _() function using the given translation domain. Given a translation domain, install a _() function using gettext's install() function. The main difference from gettext.install() is that we allow overriding the default localedir (e.g. /usr/share/locale) using a translation-domain-specific environment variable (e.g. NOVA_LOCALEDIR). :param domain: the translation domain """ from six import moves tf = _factory.TranslatorFactory(domain) moves.builtins.__dict__['_'] = tf.primary _AVAILABLE_LANGUAGES = {} def get_available_languages(domain): """Lists the available languages for the given translation domain. :param domain: the domain to get languages for """ if domain in _AVAILABLE_LANGUAGES: return copy.copy(_AVAILABLE_LANGUAGES[domain]) localedir = os.environ.get(_locale.get_locale_dir_variable_name(domain)) find = lambda x: gettext.find(domain, localedir=localedir, languages=[x]) # NOTE(mrodden): en_US should always be available (and first in case # order matters) since our in-line message strings are en_US language_list = ['en_US'] locale_identifiers = localedata.locale_identifiers() language_list.extend(language for language in locale_identifiers if find(language)) # In Babel 1.3, locale_identifiers() doesn't list some OpenStack supported # locales (e.g. 'zh_CN', and 'zh_TW') so we add the locales explicitly if # necessary so that they are listed as supported. aliases = {'zh': 'zh_CN', 'zh_Hant_HK': 'zh_HK', 'zh_Hant': 'zh_TW', 'fil': 'tl_PH'} language_list.extend(alias for locale, alias in aliases.items() if (locale in language_list and alias not in language_list)) language_list.extend(alias for locale, alias in aliases.items() if (locale not in language_list and find(alias))) # In webob.acceptparse, the best_match is just match the first element in # the language_list, so make the precise element in front result = ['en_US'] for i in language_list[1:]: if '_' in i: result.insert(1, i) else: result.append(i) _AVAILABLE_LANGUAGES[domain] = result return copy.copy(result) _original_find = gettext.find _FIND_CACHE = {} def cached_find(domain, localedir=None, languages=None, all=0): """A version of gettext.find using a cache. gettext.find looks for mo files on the disk using os.path.exists. Those don't tend to change over time, but the system calls pile up with a long-running service. This caches the result so that we return the same mo files, and only call find once per domain. """ key = (domain, localedir, tuple(languages) if languages is not None else None, all) if key in _FIND_CACHE: return _FIND_CACHE[key] result = _original_find(domain, localedir, languages, all) _FIND_CACHE[key] = result return result gettext.find = cached_find oslo.i18n-4.0.1/oslo_i18n/_translate.py0000664000175000017500000000546213643050246017623 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import six __all__ = [ 'translate', ] def translate(obj, desired_locale=None): """Gets the translated unicode representation of the given object. If the object is not translatable it is returned as-is. If the desired_locale argument is None the object is translated to the system locale. :param obj: the object to translate :param desired_locale: the locale to translate the message to, if None the default system locale will be used :returns: the translated object in unicode, or the original object if it could not be translated """ from oslo_i18n import _message # avoid circular dependency at module level message = obj if not isinstance(message, _message.Message): # If the object to translate is not already translatable, # let's first get its unicode representation message = six.text_type(obj) if isinstance(message, _message.Message): # Even after unicoding() we still need to check if we are # running with translatable unicode before translating return message.translation(desired_locale) return obj def translate_args(args, desired_locale=None): """Translates all the translatable elements of the given arguments object. This method is used for translating the translatable values in method arguments which include values of tuples or dictionaries. If the object is not a tuple or a dictionary the object itself is translated if it is translatable. If the locale is None the object is translated to the system locale. :param args: the args to translate :param desired_locale: the locale to translate the args to, if None the default system locale will be used :returns: a new args object with the translated contents of the original """ if isinstance(args, tuple): return tuple(translate(v, desired_locale) for v in args) if isinstance(args, dict): translated_dict = dict((key, translate(value, desired_locale)) for key, value in args.items()) return translated_dict return translate(args, desired_locale) oslo.i18n-4.0.1/oslo_i18n/_locale.py0000664000175000017500000000164713643050246017066 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. def get_locale_dir_variable_name(domain): """Build environment variable name for local dir. Convert a translation domain name to a variable for specifying a separate locale dir. """ return domain.upper().replace('.', '_').replace('-', '_') + '_LOCALEDIR' oslo.i18n-4.0.1/oslo_i18n/_i18n.py0000664000175000017500000000164613643050246016405 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Translation support for messages in this library. """ from oslo_i18n import _factory # Create the global translation functions. _translators = _factory.TranslatorFactory('oslo_i18n') # The primary translation function using the well-known name "_" _ = _translators.primary oslo.i18n-4.0.1/oslo_i18n/_message.py0000664000175000017500000002674713643050246017263 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Private Message class for lazy translation support. """ import copy import gettext import locale import logging import os import warnings import six from oslo_i18n import _locale from oslo_i18n import _translate # magic gettext number to separate context from message CONTEXT_SEPARATOR = "\x04" LOG = logging.getLogger(__name__) class Message(six.text_type): """A Message object is a unicode object that can be translated. Translation of Message is done explicitly using the translate() method. For all non-translation intents and purposes, a Message is simply unicode, and can be treated as such. """ def __new__(cls, msgid, msgtext=None, params=None, domain='oslo', has_contextual_form=False, has_plural_form=False, *args): """Create a new Message object. In order for translation to work gettext requires a message ID, this msgid will be used as the base unicode text. It is also possible for the msgid and the base unicode text to be different by passing the msgtext parameter. """ # If the base msgtext is not given, we use the default translation # of the msgid (which is in English) just in case the system locale is # not English, so that the base text will be in that locale by default. if not msgtext: msgtext = Message._translate_msgid(msgid, domain) # We want to initialize the parent unicode with the actual object that # would have been plain unicode if 'Message' was not enabled. msg = super(Message, cls).__new__(cls, msgtext) msg.msgid = msgid msg.domain = domain msg.params = params msg.has_contextual_form = has_contextual_form msg.has_plural_form = has_plural_form return msg def translate(self, desired_locale=None): """DEPRECATED: Use ``translation`` instead This is a compatibility shim to allow callers a chance to move away from using this function, which shadows a built-in function from our parent class. """ # We did a bad thing here. We shadowed the unicode built-in translate, # which means there are circumstances where this function may be called # with a desired_locale that is a non-string sequence or mapping type. # This will not only result in incorrect behavior, it also fails # because things like lists are not hashable, and we use the value in # desired_locale as part of a dict key. If we see a non-string # desired_locale, we know that the caller did not intend to call this # form of translate and we should instead pass that along to the # unicode implementation of translate. # # Unfortunately this doesn't entirely solve the problem as it would be # possible for a caller to use a string as the mapping type and in that # case we can't tell which version of translate they intended to call. # That doesn't seem to be a common thing to do though. str.maketrans # returns a dict, and that is probably the way most callers will create # their mapping. if (desired_locale is not None and not isinstance(desired_locale, six.string_types)): return super(Message, self).translate(desired_locale) warnings.warn('Message.translate called with a string argument. ' 'If your intent was to translate the message into ' 'another language, please call Message.translation ' 'instead. If your intent was to call "translate" as ' 'defined by the str/unicode type, please use a dict or ' 'list mapping instead. String mappings will not work ' 'until this compatibility shim is removed.') return self.translation(desired_locale) def translation(self, desired_locale=None): """Translate this message to the desired locale. :param desired_locale: The desired locale to translate the message to, if no locale is provided the message will be translated to the system's default locale. :returns: the translated message in unicode """ translated_message = Message._translate_msgid(self.msgid, self.domain, desired_locale, self.has_contextual_form, self.has_plural_form) if self.params is None: # No need for more translation return translated_message # This Message object may have been formatted with one or more # Message objects as substitution arguments, given either as a single # argument, part of a tuple, or as one or more values in a dictionary. # When translating this Message we need to translate those Messages too translated_params = _translate.translate_args(self.params, desired_locale) return self._safe_translate(translated_message, translated_params) @staticmethod def _translate_msgid(msgid, domain, desired_locale=None, has_contextual_form=False, has_plural_form=False): if not desired_locale: system_locale = locale.getdefaultlocale() # If the system locale is not available to the runtime use English if not system_locale or not system_locale[0]: desired_locale = 'en_US' else: desired_locale = system_locale[0] locale_dir = os.environ.get( _locale.get_locale_dir_variable_name(domain) ) lang = gettext.translation(domain, localedir=locale_dir, languages=[desired_locale], fallback=True) if not has_contextual_form and not has_plural_form: # This is the most common case, so check it first. translator = lang.gettext if six.PY3 else lang.ugettext translated_message = translator(msgid) elif has_contextual_form and has_plural_form: # Reserved for contextual and plural translation function, # which is not yet implemented. raise ValueError("Unimplemented.") elif has_contextual_form: (msgctx, msgtxt) = msgid translator = lang.gettext if six.PY3 else lang.ugettext msg_with_ctx = "%s%s%s" % (msgctx, CONTEXT_SEPARATOR, msgtxt) translated_message = translator(msg_with_ctx) if CONTEXT_SEPARATOR in translated_message: # Translation not found, use the original text translated_message = msgtxt elif has_plural_form: (msgsingle, msgplural, msgcount) = msgid translator = lang.ngettext if six.PY3 else lang.ungettext translated_message = translator(msgsingle, msgplural, msgcount) return translated_message def _safe_translate(self, translated_message, translated_params): """Trap translation errors and fall back to default translation. :param translated_message: the requested translation :param translated_params: the params to be inserted :return: if parameter insertion is successful then it is the translated_message with the translated_params inserted, if the requested translation fails then it is the default translation with the params """ try: translated_message = translated_message % translated_params except (KeyError, TypeError) as err: # KeyError for parameters named in the translated_message # but not found in translated_params and TypeError for # type strings that do not match the type of the # parameter. # # Log the error translating the message and use the # original message string so the translator's bad message # catalog doesn't break the caller. # Do not translate this log message even if it is used as a # warning message as a wrong translation of this message could # cause infinite recursion msg = (u'Failed to insert replacement values into translated ' u'message %s (Original: %r): %s') warnings.warn(msg % (translated_message, self.msgid, err)) LOG.debug(msg, translated_message, self.msgid, err) translated_message = self.msgid % translated_params return translated_message def __mod__(self, other): # When we mod a Message we want the actual operation to be performed # by the base class (i.e. unicode()), the only thing we do here is # save the original msgid and the parameters in case of a translation params = self._sanitize_mod_params(other) unicode_mod = self._safe_translate(six.text_type(self), params) modded = Message(self.msgid, msgtext=unicode_mod, params=params, domain=self.domain) return modded def _sanitize_mod_params(self, other): """Sanitize the object being modded with this Message. - Add support for modding 'None' so translation supports it - Trim the modded object, which can be a large dictionary, to only those keys that would actually be used in a translation - Snapshot the object being modded, in case the message is translated, it will be used as it was when the Message was created """ if other is None: params = (other,) elif isinstance(other, dict): # Merge the dictionaries # Copy each item in case one does not support deep copy. params = {} if isinstance(self.params, dict): params.update((key, self._copy_param(val)) for key, val in self.params.items()) params.update((key, self._copy_param(val)) for key, val in other.items()) else: params = self._copy_param(other) return params def _copy_param(self, param): try: return copy.deepcopy(param) except Exception: # Fallback to casting to unicode this will handle the # python code-like objects that can't be deep-copied return six.text_type(param) def __add__(self, other): from oslo_i18n._i18n import _ msg = _('Message objects do not support addition.') raise TypeError(msg) def __radd__(self, other): return self.__add__(other) oslo.i18n-4.0.1/oslo_i18n/locale/0000775000175000017500000000000013643050342016342 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/pt/0000775000175000017500000000000013643050342016765 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/pt/LC_MESSAGES/0000775000175000017500000000000013643050342020552 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/pt/LC_MESSAGES/oslo_i18n.po0000664000175000017500000000161413643050246022732 0ustar zuulzuul00000000000000# Translations template for oslo.i18n. # Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the oslo.i18n project. # # Translators: # MMSRS , 2015 # Andreas Jaeger , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.i18n 3.6.1.dev1\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2016-06-04 05:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2015-08-11 05:02+0000\n" "Last-Translator: MMSRS \n" "Language: pt\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Generated-By: Babel 2.0\n" "X-Generator: Zanata 3.7.3\n" "Language-Team: Portuguese\n" msgid "Message objects do not support addition." msgstr "Os objetos de mensagem não suportam a adição." oslo.i18n-4.0.1/oslo_i18n/locale/en_GB/0000775000175000017500000000000013643050342017314 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000013643050342021101 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/en_GB/LC_MESSAGES/oslo_i18n.po0000664000175000017500000000163013643050246023257 0ustar zuulzuul00000000000000# Translations template for oslo.i18n. # Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the oslo.i18n project. # # Translators: # Andi Chandler , 2014 # Andreas Jaeger , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.i18n VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-05 13:14+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-04-12 06:17+0000\n" "Last-Translator: Copied by Zanata \n" "Language: en_GB\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Generated-By: Babel 2.0\n" "X-Generator: Zanata 4.3.3\n" "Language-Team: English (United Kingdom)\n" msgid "Message objects do not support addition." msgstr "Message objects do not support addition." oslo.i18n-4.0.1/oslo_i18n/locale/zh_CN/0000775000175000017500000000000013643050342017343 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/zh_CN/LC_MESSAGES/0000775000175000017500000000000013643050342021130 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/zh_CN/LC_MESSAGES/oslo_i18n.po0000664000175000017500000000160213643050246023305 0ustar zuulzuul00000000000000# Translations template for oslo.i18n. # Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the oslo.i18n project. # # Translators: # Xiao Xi LIU , 2014 # Andreas Jaeger , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.i18n VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-05 13:14+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-04-12 06:17+0000\n" "Last-Translator: Copied by Zanata \n" "Language: zh_CN\n" "Plural-Forms: nplurals=1; plural=0;\n" "Generated-By: Babel 2.0\n" "X-Generator: Zanata 4.3.3\n" "Language-Team: Chinese (China)\n" msgid "Message objects do not support addition." msgstr "消息对象不支持添加操作。" oslo.i18n-4.0.1/oslo_i18n/locale/ja/0000775000175000017500000000000013643050342016734 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/ja/LC_MESSAGES/0000775000175000017500000000000013643050342020521 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/ja/LC_MESSAGES/oslo_i18n.po0000664000175000017500000000127713643050246022706 0ustar zuulzuul00000000000000# Andreas Jaeger , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.i18n 3.6.1.dev1\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2016-06-04 05:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-02-20 06:44+0000\n" "Last-Translator: KATO Tomoyuki \n" "Language-Team: Japanese\n" "Language: ja\n" "X-Generator: Zanata 3.7.3\n" "Plural-Forms: nplurals=1; plural=0\n" msgid "Message objects do not support addition." msgstr "メッセージオブジェクトは追加機能をサポートしていません。" oslo.i18n-4.0.1/oslo_i18n/locale/fr/0000775000175000017500000000000013643050342016751 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/fr/LC_MESSAGES/0000775000175000017500000000000013643050342020536 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/fr/LC_MESSAGES/oslo_i18n.po0000664000175000017500000000171413643050246022717 0ustar zuulzuul00000000000000# Translations template for oslo.i18n. # Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the oslo.i18n project. # # Translators: # Jonathan Dupart , 2014 # Maxime COQUEREL , 2014 # Andreas Jaeger , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.i18n 3.6.1.dev1\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2016-06-04 05:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2014-09-25 09:36+0000\n" "Last-Translator: Jonathan Dupart \n" "Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "Generated-By: Babel 2.0\n" "X-Generator: Zanata 3.7.3\n" "Language-Team: French\n" msgid "Message objects do not support addition." msgstr "Les objects message ne supportent pas l'ajout." oslo.i18n-4.0.1/oslo_i18n/locale/de/0000775000175000017500000000000013643050342016732 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/de/LC_MESSAGES/0000775000175000017500000000000013643050342020517 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/de/LC_MESSAGES/oslo_i18n.po0000664000175000017500000000163413643050246022701 0ustar zuulzuul00000000000000# Translations template for oslo.i18n. # Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the oslo.i18n project. # # Translators: # Andreas Jaeger , 2014 # Robert Simai, 2015 # Andreas Jaeger , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.i18n 3.6.1.dev1\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2016-06-04 05:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-06-01 09:23+0000\n" "Last-Translator: Andreas Jaeger \n" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Generated-By: Babel 2.0\n" "X-Generator: Zanata 3.7.3\n" "Language-Team: German\n" msgid "Message objects do not support addition." msgstr "Message-Objekte unterstützen keine Addition." oslo.i18n-4.0.1/oslo_i18n/locale/it/0000775000175000017500000000000013643050342016756 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/it/LC_MESSAGES/0000775000175000017500000000000013643050342020543 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/it/LC_MESSAGES/oslo_i18n.po0000664000175000017500000000162013643050246022720 0ustar zuulzuul00000000000000# Translations template for oslo.i18n. # Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the oslo.i18n project. # # Translators: # PierAlberto , 2014 # Andreas Jaeger , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.i18n 3.6.1.dev1\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2016-06-04 05:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2014-08-24 11:27+0000\n" "Last-Translator: PierAlberto \n" "Language: it\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Generated-By: Babel 2.0\n" "X-Generator: Zanata 3.7.3\n" "Language-Team: Italian\n" msgid "Message objects do not support addition." msgstr "I messaggi oggetti non supportano aggiunte." oslo.i18n-4.0.1/oslo_i18n/locale/ko_KR/0000775000175000017500000000000013643050342017347 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/ko_KR/LC_MESSAGES/0000775000175000017500000000000013643050342021134 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/ko_KR/LC_MESSAGES/oslo_i18n.po0000664000175000017500000000163513643050246023317 0ustar zuulzuul00000000000000# Translations template for oslo.i18n. # Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the oslo.i18n project. # # Translators: # Sungjin Kang , 2014 # Andreas Jaeger , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.i18n VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-05 13:14+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-04-12 06:17+0000\n" "Last-Translator: Copied by Zanata \n" "Language: ko_KR\n" "Plural-Forms: nplurals=1; plural=0;\n" "Generated-By: Babel 2.0\n" "X-Generator: Zanata 4.3.3\n" "Language-Team: Korean (South Korea)\n" msgid "Message objects do not support addition." msgstr "메시지 객체는 추가 지원을하지 않습니다." oslo.i18n-4.0.1/oslo_i18n/locale/pl_PL/0000775000175000017500000000000013643050342017350 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/pl_PL/LC_MESSAGES/0000775000175000017500000000000013643050342021135 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/pl_PL/LC_MESSAGES/oslo_i18n.po0000664000175000017500000000171713643050246023321 0ustar zuulzuul00000000000000# Translations template for oslo.i18n. # Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the oslo.i18n project. # # Translators: # Łukasz Jernaś , 2014 # Andreas Jaeger , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.i18n VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-05 13:14+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-04-12 06:17+0000\n" "Last-Translator: Copied by Zanata \n" "Language: pl_PL\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2);\n" "Generated-By: Babel 2.0\n" "X-Generator: Zanata 4.3.3\n" "Language-Team: Polish (Poland)\n" msgid "Message objects do not support addition." msgstr "Obiekty Message nie wspierają dodawania." oslo.i18n-4.0.1/oslo_i18n/locale/es/0000775000175000017500000000000013643050342016751 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/es/LC_MESSAGES/0000775000175000017500000000000013643050342020536 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/locale/es/LC_MESSAGES/oslo_i18n.po0000664000175000017500000000163213643050246022716 0ustar zuulzuul00000000000000# Translations template for oslo.i18n. # Copyright (C) 2015 ORGANIZATION # This file is distributed under the same license as the oslo.i18n project. # # Translators: # Adriana Chisco Landazábal , 2015 # Andreas Jaeger , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.i18n 3.6.1.dev1\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2016-06-04 05:29+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2015-06-22 09:03+0000\n" "Last-Translator: Adriana Chisco Landazábal \n" "Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Generated-By: Babel 2.0\n" "X-Generator: Zanata 3.7.3\n" "Language-Team: Spanish\n" msgid "Message objects do not support addition." msgstr "Objectos de mensaje no soportan adición." oslo.i18n-4.0.1/oslo_i18n/log.py0000664000175000017500000000672613643050246016254 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """logging utilities for translation """ from logging import handlers from oslo_i18n import _translate class TranslationHandler(handlers.MemoryHandler): """Handler that translates records before logging them. When lazy translation is enabled in the application (see :func:`~oslo_i18n.enable_lazy`), the :class:`TranslationHandler` uses its locale configuration setting to determine how to translate LogRecord objects before forwarding them to the logging.Handler. When lazy translation is disabled, the message in the LogRecord is converted to unicode without any changes and then forwarded to the logging.Handler. The handler can be configured declaratively in the ``logging.conf`` as follows:: [handlers] keys = translatedlog, translator [handler_translatedlog] class = handlers.WatchedFileHandler args = ('/var/log/api-localized.log',) formatter = context [handler_translator] class = oslo_i18n.log.TranslationHandler target = translatedlog args = ('zh_CN',) If the specified locale is not available in the system, the handler will log in the default locale. """ def __init__(self, locale=None, target=None): """Initialize a TranslationHandler :param locale: locale to use for translating messages :param target: logging.Handler object to forward LogRecord objects to after translation """ # NOTE(luisg): In order to allow this handler to be a wrapper for # other handlers, such as a FileHandler, and still be able to # configure it using logging.conf, this handler has to extend # MemoryHandler because only the MemoryHandlers' logging.conf # parsing is implemented such that it accepts a target handler. handlers.MemoryHandler.__init__(self, capacity=0, target=target) self.locale = locale def setFormatter(self, fmt): self.target.setFormatter(fmt) def emit(self, record): # We save the message from the original record to restore it # after translation, so other handlers are not affected by this original_msg = record.msg original_args = record.args try: self._translate_and_log_record(record) finally: record.msg = original_msg record.args = original_args def _translate_and_log_record(self, record): record.msg = _translate.translate(record.msg, self.locale) # In addition to translating the message, we also need to translate # arguments that were passed to the log method that were not part # of the main message e.g., log.info(_('Some message %s'), this_one)) record.args = _translate.translate_args(record.args, self.locale) self.target.emit(record) oslo.i18n-4.0.1/oslo_i18n/fixture.py0000664000175000017500000001313313643050246017147 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Test fixtures for working with oslo_i18n. """ import gettext import fixtures import six from oslo_i18n import _lazy from oslo_i18n import _message class Translation(fixtures.Fixture): """Fixture for managing translatable strings. This class provides methods for creating translatable strings using both lazy translation and immediate translation. It can be used to generate the different types of messages returned from oslo_i18n to test code that may need to know about the type to handle them differently (for example, error handling in WSGI apps, or logging). Use this class to generate messages instead of toggling the global lazy flag and using the regular translation factory. """ def __init__(self, domain='test-domain'): """Initialize the fixture. :param domain: The translation domain. This is not expected to coincide with an actual set of message catalogs, but it can. :type domain: str """ self.domain = domain def lazy(self, msg): """Return a lazily translated message. :param msg: Input message string. May optionally include positional or named string interpolation markers. :type msg: str or unicode """ return _message.Message(msg, domain=self.domain) def immediate(self, msg): """Return a string as though it had been translated immediately. :param msg: Input message string. May optionally include positional or named string interpolation markers. :type msg: str or unicode """ return six.text_type(msg) class ToggleLazy(fixtures.Fixture): """Fixture to toggle lazy translation on or off for a test.""" def __init__(self, enabled): """Force lazy translation on or off. :param enabled: Flag controlling whether to enable or disable lazy translation, passed to :func:`~oslo_i18n.enable_lazy`. :type enabled: bool """ super(ToggleLazy, self).__init__() self._enabled = enabled self._original_value = _lazy.USE_LAZY def setUp(self): super(ToggleLazy, self).setUp() self.addCleanup(self._restore_original) _lazy.enable_lazy(self._enabled) def _restore_original(self): _lazy.enable_lazy(self._original_value) class _PrefixTranslator(gettext.NullTranslations): """Translator that adds prefix to message ids NOTE: gettext.NullTranslations is an old style class :parm prefix: prefix to add to message id. If not specified (None) then 'noprefix' is used. :type prefix: string """ def __init__(self, fp=None, prefix='noprefix'): gettext.NullTranslations.__init__(self, fp) self.prefix = prefix def gettext(self, message): msg = gettext.NullTranslations.gettext(self, message) return self.prefix + msg def ugettext(self, message): msg = gettext.NullTranslations.ugettext(self, message) return self.prefix + msg def _prefix_translations(*x, **y): """Use message id prefixed with domain and language as translation """ return _PrefixTranslator(prefix=x[0] + '/' + y['languages'][0] + ': ') class PrefixLazyTranslation(fixtures.Fixture): """Fixture to prefix lazy translation enabled messages Use of this fixture will cause messages supporting lazy translation to be replaced with the message id prefixed with 'domain/language:'. For example, 'oslo/en_US: message about something'. It will also override the available languages returned from oslo_18n.get_available_languages to the specified languages. This will enable tests to ensure that messages were translated lazily with the specified language and not immediately with the default language. NOTE that this does not work unless lazy translation is enabled, so it uses the ToggleLazy fixture to enable lazy translation. :param languages: list of languages to support. If not specified (None) then ['en_US'] is used. :type languages: list of strings """ _DEFAULT_LANG = 'en_US' def __init__(self, languages=None, locale=None): super(PrefixLazyTranslation, self).__init__() self.languages = languages or [PrefixLazyTranslation._DEFAULT_LANG] self.locale = locale def setUp(self): super(PrefixLazyTranslation, self).setUp() self.useFixture(ToggleLazy(True)) self.useFixture(fixtures.MonkeyPatch( 'oslo_i18n._gettextutils.get_available_languages', lambda *x, **y: self.languages)) self.useFixture(fixtures.MonkeyPatch( 'oslo_i18n.get_available_languages', lambda *x, **y: self.languages)) self.useFixture(fixtures.MonkeyPatch('gettext.translation', _prefix_translations)) self.useFixture(fixtures.MonkeyPatch('locale.getdefaultlocale', lambda *x, **y: self.locale)) oslo.i18n-4.0.1/oslo_i18n/tests/0000775000175000017500000000000013643050342016245 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/tests/test_translate.py0000664000175000017500000000322313643050246021656 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from __future__ import unicode_literals from unittest import mock from oslotest import base as test_base from oslo_i18n import _message from oslo_i18n import _translate from oslo_i18n.tests import fakes from oslo_i18n.tests import utils class TranslateTest(test_base.BaseTestCase): @mock.patch('gettext.translation') def test_translate(self, mock_translation): en_message = 'A message in the default locale' es_translation = 'A message in Spanish' message = _message.Message(en_message) es_translations = {en_message: es_translation} translations_map = {'es': es_translations} translator = fakes.FakeTranslations.translator(translations_map) mock_translation.side_effect = translator # translate() works on msgs and on objects whose unicode reps are msgs obj = utils.SomeObject(message) self.assertEqual(es_translation, _translate.translate(message, 'es')) self.assertEqual(es_translation, _translate.translate(obj, 'es')) oslo.i18n-4.0.1/oslo_i18n/tests/fakes.py0000664000175000017500000000373013643050246017716 0ustar zuulzuul00000000000000# Copyright 2012 Intel Inc, OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Fakes For translation tests. """ import gettext class FakeTranslations(gettext.GNUTranslations): """A test GNUTranslations class that takes a map of msg -> translations.""" def __init__(self, translations): self.translations = translations # used by Python 3 def gettext(self, msgid): return self.translations.get(msgid, msgid) # used by Python 2 def ugettext(self, msgid): return self.translations.get(msgid, msgid) @staticmethod def translator(locales_map): """Build mock translator for the given locales. Returns a mock gettext.translation function that uses individual TestTranslations to translate in the given locales. :param locales_map: A map from locale name to a translations map. { 'es': {'Hi': 'Hola', 'Bye': 'Adios'}, 'zh': {'Hi': 'Ni Hao', 'Bye': 'Zaijian'} } """ def _translation(domain, localedir=None, languages=None, fallback=None): if languages: language = languages[0] if language in locales_map: return FakeTranslations(locales_map[language]) return gettext.NullTranslations() return _translation oslo.i18n-4.0.1/oslo_i18n/tests/test_public_api.py0000664000175000017500000000252113643050246021770 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """A few tests that use the public API to ensure the imports work.""" import unittest from unittest import mock import oslo_i18n from oslo_i18n import _lazy class PublicAPITest(unittest.TestCase): def test_create_factory(self): oslo_i18n.TranslatorFactory('domain') def test_install(self): with mock.patch('six.moves.builtins'): oslo_i18n.install('domain') def test_get_available_languages(self): oslo_i18n.get_available_languages('domains') def test_toggle_lazy(self): original = _lazy.USE_LAZY try: oslo_i18n.enable_lazy(True) oslo_i18n.enable_lazy(False) finally: oslo_i18n.enable_lazy(original) def test_translate(self): oslo_i18n.translate(u'string') oslo.i18n-4.0.1/oslo_i18n/tests/test_locale_dir_variable.py0000664000175000017500000000231013643050246023617 0ustar zuulzuul00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslotest import base as test_base import testscenarios.testcase from oslo_i18n import _locale class LocaleDirVariableTest(testscenarios.testcase.WithScenarios, test_base.BaseTestCase): scenarios = [ ('simple', {'domain': 'simple', 'expected': 'SIMPLE_LOCALEDIR'}), ('with_dot', {'domain': 'one.two', 'expected': 'ONE_TWO_LOCALEDIR'}), ('with_dash', {'domain': 'one-two', 'expected': 'ONE_TWO_LOCALEDIR'}), ] def test_make_variable_name(self): var = _locale.get_locale_dir_variable_name(self.domain) self.assertEqual(self.expected, var) oslo.i18n-4.0.1/oslo_i18n/tests/test_message.py0000664000175000017500000007314713643050246021321 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from __future__ import unicode_literals import logging from unittest import mock import warnings from oslotest import base as test_base import six import testtools from oslo_i18n import _message from oslo_i18n.tests import fakes from oslo_i18n.tests import utils LOG = logging.getLogger(__name__) class MessageTestCase(test_base.BaseTestCase): """Unit tests for locale Message class.""" def test_message_id_and_message_text(self): message = _message.Message('1') self.assertEqual('1', message.msgid) self.assertEqual('1', message) message = _message.Message('1', msgtext='A') self.assertEqual('1', message.msgid) self.assertEqual('A', message) def test_message_is_unicode(self): message = _message.Message('some %s') % 'message' self.assertIsInstance(message, six.text_type) @mock.patch('locale.getdefaultlocale') @mock.patch('gettext.translation') def test_create_message_non_english_default_locale(self, mock_translation, mock_getdefaultlocale): msgid = 'A message in English' es_translation = 'A message in Spanish' es_translations = {msgid: es_translation} translations_map = {'es': es_translations} translator = fakes.FakeTranslations.translator(translations_map) mock_translation.side_effect = translator mock_getdefaultlocale.return_value = ('es',) message = _message.Message(msgid) # The base representation of the message is in Spanish, as well as # the default translation, since the default locale was Spanish. self.assertEqual(es_translation, message) self.assertEqual(es_translation, message.translation()) def test_translation_returns_unicode(self): message = _message.Message('some %s') % 'message' self.assertIsInstance(message.translation(), six.text_type) def test_mod_with_named_parameters(self): msgid = ("%(description)s\nCommand: %(cmd)s\n" "Exit code: %(exit_code)s\nStdout: %(stdout)r\n" "Stderr: %(stderr)r %%(something)s") params = {'description': 'test1', 'cmd': 'test2', 'exit_code': 'test3', 'stdout': 'test4', 'stderr': 'test5', 'something': 'trimmed'} result = _message.Message(msgid) % params expected = msgid % params self.assertEqual(expected, result) self.assertEqual(expected, result.translation()) def test_multiple_mod_with_named_parameter(self): msgid = ("%(description)s\nCommand: %(cmd)s\n" "Exit code: %(exit_code)s\nStdout: %(stdout)r\n" "Stderr: %(stderr)r") params = {'description': 'test1', 'cmd': 'test2', 'exit_code': 'test3', 'stdout': 'test4', 'stderr': 'test5'} # Run string interpolation the first time to make a new Message first = _message.Message(msgid) % params # Run string interpolation on the new Message, to replicate # one of the error paths with some Exception classes we've # implemented in OpenStack. We should receive a second Message # object, but the translation results should be the same. # # The production code that triggers this problem does something # like: # # msg = _('there was a problem %(name)s') % {'name': 'some value'} # LOG.error(msg) # raise BadExceptionClass(msg) # # where BadExceptionClass does something like: # # class BadExceptionClass(Exception): # def __init__(self, msg, **kwds): # super(BadExceptionClass, self).__init__(msg % kwds) # expected = first % {} # Base message id should be the same self.assertEqual(first.msgid, expected.msgid) # Preserved arguments should be the same self.assertEqual(first.params, expected.params) # Should have different objects self.assertIsNot(expected, first) # Final translations should be the same self.assertEqual(expected.translation(), first.translation()) def test_mod_with_named_parameters_no_space(self): msgid = ("Request: %(method)s http://%(server)s:" "%(port)s%(url)s with headers %(headers)s") params = {'method': 'POST', 'server': 'test1', 'port': 1234, 'url': 'test2', 'headers': {'h1': 'val1'}} result = _message.Message(msgid) % params expected = msgid % params self.assertEqual(expected, result) self.assertEqual(expected, result.translation()) def test_mod_with_dict_parameter(self): msgid = "Test that we can inject a dictionary %s" params = {'description': 'test1'} result = _message.Message(msgid) % params expected = msgid % params self.assertEqual(expected, result) self.assertEqual(expected, result.translation()) def test_mod_with_wrong_field_type_in_trans(self): msgid = "Correct type %(arg1)s" params = {'arg1': 'test1'} with mock.patch('gettext.translation') as trans: # Set up ugettext to return the original message with the # correct format string. trans.return_value.ugettext.return_value = msgid # Build a message and give it some parameters. result = _message.Message(msgid) % params # Now set up ugettext to return the translated version of # the original message, with a bad format string. wrong_type = u'Wrong type %(arg1)d' if six.PY3: trans.return_value.gettext.return_value = wrong_type else: trans.return_value.ugettext.return_value = wrong_type trans_result = result.translation() expected = msgid % params self.assertEqual(expected, trans_result) def test_mod_with_wrong_field_type(self): msgid = "Test that we handle unused args %(arg1)d" params = {'arg1': 'test1'} with testtools.ExpectedException(TypeError): _message.Message(msgid) % params def test_mod_with_missing_arg(self): msgid = "Test that we handle missing args %(arg1)s %(arg2)s" params = {'arg1': 'test1'} with testtools.ExpectedException(KeyError, '.*arg2.*'): _message.Message(msgid) % params def test_mod_with_integer_parameters(self): msgid = "Some string with params: %d" params = [0, 1, 10, 24124] messages = [] results = [] for param in params: messages.append(msgid % param) results.append(_message.Message(msgid) % param) for message, result in zip(messages, results): self.assertEqual(type(result), _message.Message) self.assertEqual(message, result.translation()) # simulate writing out as string result_str = '%s' % result.translation() self.assertEqual(result_str, message) self.assertEqual(message, result) def test_mod_copies_parameters(self): msgid = "Found object: %(current_value)s" changing_dict = {'current_value': 1} # A message created with some params result = _message.Message(msgid) % changing_dict # The parameters may change changing_dict['current_value'] = 2 # Even if the param changes when the message is # translated it should use the original param self.assertEqual('Found object: 1', result.translation()) def test_mod_deep_copies_parameters(self): msgid = "Found list: %(current_list)s" changing_list = list([1, 2, 3]) params = {'current_list': changing_list} # Apply the params result = _message.Message(msgid) % params # Change the list changing_list.append(4) # Even though the list changed the message # translation should use the original list self.assertEqual("Found list: [1, 2, 3]", result.translation()) def test_mod_deep_copies_param_nodeep_param(self): msgid = "Value: %s" params = utils.NoDeepCopyObject(5) # Apply the params result = _message.Message(msgid) % params self.assertEqual("Value: 5", result.translation()) def test_mod_deep_copies_param_nodeep_dict(self): msgid = "Values: %(val1)s %(val2)s" params = {'val1': 1, 'val2': utils.NoDeepCopyObject(2)} # Apply the params result = _message.Message(msgid) % params self.assertEqual("Values: 1 2", result.translation()) # Apply again to make sure other path works as well params = {'val1': 3, 'val2': utils.NoDeepCopyObject(4)} result = _message.Message(msgid) % params self.assertEqual("Values: 3 4", result.translation()) def test_mod_returns_a_copy(self): msgid = "Some msgid string: %(test1)s %(test2)s" message = _message.Message(msgid) m1 = message % {'test1': 'foo', 'test2': 'bar'} m2 = message % {'test1': 'foo2', 'test2': 'bar2'} self.assertIsNot(message, m1) self.assertIsNot(message, m2) self.assertEqual(m1.translation(), msgid % {'test1': 'foo', 'test2': 'bar'}) self.assertEqual(m2.translation(), msgid % {'test1': 'foo2', 'test2': 'bar2'}) def test_mod_with_none_parameter(self): msgid = "Some string with params: %s" message = _message.Message(msgid) % None self.assertEqual(msgid % None, message) self.assertEqual(msgid % None, message.translation()) def test_mod_with_missing_parameters(self): msgid = "Some string with params: %s %s" test_me = lambda: _message.Message(msgid) % 'just one' # Just like with strings missing parameters raise TypeError self.assertRaises(TypeError, test_me) def test_mod_with_extra_parameters(self): msgid = "Some string with params: %(param1)s %(param2)s" params = {'param1': 'test', 'param2': 'test2', 'param3': 'notinstring'} result = _message.Message(msgid) % params expected = msgid % params self.assertEqual(expected, result) self.assertEqual(expected, result.translation()) # Make sure unused params still there self.assertEqual(params.keys(), result.params.keys()) def test_add_disabled(self): msgid = "A message" test_me = lambda: _message.Message(msgid) + ' some string' self.assertRaises(TypeError, test_me) def test_radd_disabled(self): msgid = "A message" test_me = lambda: utils.SomeObject('test') + _message.Message(msgid) self.assertRaises(TypeError, test_me) @mock.patch('gettext.translation') def test_translation(self, mock_translation): en_message = 'A message in the default locale' es_translation = 'A message in Spanish' message = _message.Message(en_message) es_translations = {en_message: es_translation} translations_map = {'es': es_translations} translator = fakes.FakeTranslations.translator(translations_map) mock_translation.side_effect = translator self.assertEqual(es_translation, message.translation('es')) @mock.patch('gettext.translation') def test_translate_message_from_unicoded_object(self, mock_translation): en_message = 'A message in the default locale' es_translation = 'A message in Spanish' message = _message.Message(en_message) es_translations = {en_message: es_translation} translations_map = {'es': es_translations} translator = fakes.FakeTranslations.translator(translations_map) mock_translation.side_effect = translator # Here we are not testing the Message object directly but the result # of unicoding() an object whose unicode representation is a Message obj = utils.SomeObject(message) unicoded_obj = six.text_type(obj) self.assertEqual(es_translation, unicoded_obj.translation('es')) @mock.patch('gettext.translation') def test_translate_multiple_languages(self, mock_translation): en_message = 'A message in the default locale' es_translation = 'A message in Spanish' zh_translation = 'A message in Chinese' message = _message.Message(en_message) es_translations = {en_message: es_translation} zh_translations = {en_message: zh_translation} translations_map = {'es': es_translations, 'zh': zh_translations} translator = fakes.FakeTranslations.translator(translations_map) mock_translation.side_effect = translator self.assertEqual(es_translation, message.translation('es')) self.assertEqual(zh_translation, message.translation('zh')) self.assertEqual(en_message, message.translation(None)) self.assertEqual(en_message, message.translation('en')) self.assertEqual(en_message, message.translation('XX')) @mock.patch('gettext.translation') def test_translate_message_with_param(self, mock_translation): message_with_params = 'A message: %s' es_translation = 'A message in Spanish: %s' param = 'A Message param' translations = {message_with_params: es_translation} translator = fakes.FakeTranslations.translator({'es': translations}) mock_translation.side_effect = translator msg = _message.Message(message_with_params) msg = msg % param default_translation = message_with_params % param expected_translation = es_translation % param self.assertEqual(expected_translation, msg.translation('es')) self.assertEqual(default_translation, msg.translation('XX')) @mock.patch('gettext.translation') @mock.patch('oslo_i18n._message.LOG') def test_translate_message_bad_translation(self, mock_log, mock_translation): message_with_params = 'A message: %s' es_translation = 'A message in Spanish: %s %s' param = 'A Message param' translations = {message_with_params: es_translation} translator = fakes.FakeTranslations.translator({'es': translations}) mock_translation.side_effect = translator with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") msg = _message.Message(message_with_params) msg = msg % param default_translation = message_with_params % param self.assertEqual(default_translation, msg.translation('es')) self.assertEqual(1, len(w)) # Note(gibi): in python 3.4 str.__repr__ does not put the unicode # marker 'u' in front of the string representations so the test # removes that to have the same result in python 2.7 and 3.4 self.assertEqual("Failed to insert replacement values into " "translated message A message in Spanish: %s %s " "(Original: 'A message: %s'): " "not enough arguments for format string", str(w[0].message).replace("u'", "'")) mock_log.debug.assert_called_with(('Failed to insert replacement ' 'values into translated message ' '%s (Original: %r): %s'), es_translation, message_with_params, mock.ANY) @mock.patch('gettext.translation') @mock.patch('locale.getdefaultlocale', return_value=('es', '')) @mock.patch('oslo_i18n._message.LOG') def test_translate_message_bad_default_translation(self, mock_log, mock_local, mock_translation): message_with_params = 'A message: %s' es_translation = 'A message in Spanish: %s %s' param = 'A Message param' translations = {message_with_params: es_translation} translator = fakes.FakeTranslations.translator({'es': translations}) mock_translation.side_effect = translator msg = _message.Message(message_with_params) with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") msg = msg % param self.assertEqual(1, len(w)) # Note(gibi): in python 3.4 str.__repr__ does not put the unicode # marker 'u' in front of the string representations so the test # removes that to have the same result in python 2.7 and 3.4 self.assertEqual("Failed to insert replacement values into " "translated message A message in Spanish: %s %s " "(Original: 'A message: %s'): " "not enough arguments for format string", str(w[0].message).replace("u'", "'")) mock_log.debug.assert_called_with(('Failed to insert replacement ' 'values into translated message ' '%s (Original: %r): %s'), es_translation, message_with_params, mock.ANY) mock_log.reset_mock() default_translation = message_with_params % param self.assertEqual(default_translation, msg) self.assertFalse(mock_log.warning.called) @mock.patch('gettext.translation') def test_translate_message_with_object_param(self, mock_translation): message_with_params = 'A message: %s' es_translation = 'A message in Spanish: %s' param = 'A Message param' param_translation = 'A Message param in Spanish' translations = {message_with_params: es_translation, param: param_translation} translator = fakes.FakeTranslations.translator({'es': translations}) mock_translation.side_effect = translator msg = _message.Message(message_with_params) param_msg = _message.Message(param) # Here we are testing translation of a Message with another object # that can be translated via its unicode() representation, this is # very common for instance when modding an Exception with a Message obj = utils.SomeObject(param_msg) msg = msg % obj default_translation = message_with_params % param expected_translation = es_translation % param_translation self.assertEqual(expected_translation, msg.translation('es')) self.assertEqual(default_translation, msg.translation('XX')) @mock.patch('gettext.translation') def test_translate_message_with_param_from_unicoded_obj(self, mock_translation): message_with_params = 'A message: %s' es_translation = 'A message in Spanish: %s' param = 'A Message param' translations = {message_with_params: es_translation} translator = fakes.FakeTranslations.translator({'es': translations}) mock_translation.side_effect = translator msg = _message.Message(message_with_params) msg = msg % param default_translation = message_with_params % param expected_translation = es_translation % param obj = utils.SomeObject(msg) unicoded_obj = six.text_type(obj) self.assertEqual(expected_translation, unicoded_obj.translation('es')) self.assertEqual(default_translation, unicoded_obj.translation('XX')) @mock.patch('gettext.translation') def test_translate_message_with_message_parameter(self, mock_translation): message_with_params = 'A message with param: %s' es_translation = 'A message with param in Spanish: %s' message_param = 'A message param' es_param_translation = 'A message param in Spanish' translations = {message_with_params: es_translation, message_param: es_param_translation} translator = fakes.FakeTranslations.translator({'es': translations}) mock_translation.side_effect = translator msg = _message.Message(message_with_params) msg_param = _message.Message(message_param) msg = msg % msg_param default_translation = message_with_params % message_param expected_translation = es_translation % es_param_translation self.assertEqual(expected_translation, msg.translation('es')) self.assertEqual(default_translation, msg.translation('XX')) @mock.patch('gettext.translation') def test_translate_message_with_message_parameters(self, mock_translation): message_with_params = 'A message with params: %s %s' es_translation = 'A message with params in Spanish: %s %s' message_param = 'A message param' es_param_translation = 'A message param in Spanish' another_message_param = 'Another message param' another_es_param_translation = 'Another message param in Spanish' translations = {message_with_params: es_translation, message_param: es_param_translation, another_message_param: another_es_param_translation} translator = fakes.FakeTranslations.translator({'es': translations}) mock_translation.side_effect = translator msg = _message.Message(message_with_params) param_1 = _message.Message(message_param) param_2 = _message.Message(another_message_param) msg = msg % (param_1, param_2) default_translation = message_with_params % (message_param, another_message_param) expected_translation = es_translation % (es_param_translation, another_es_param_translation) self.assertEqual(expected_translation, msg.translation('es')) self.assertEqual(default_translation, msg.translation('XX')) @mock.patch('gettext.translation') def test_translate_message_with_named_parameters(self, mock_translation): message_with_params = 'A message with params: %(param)s' es_translation = 'A message with params in Spanish: %(param)s' message_param = 'A Message param' es_param_translation = 'A message param in Spanish' translations = {message_with_params: es_translation, message_param: es_param_translation} translator = fakes.FakeTranslations.translator({'es': translations}) mock_translation.side_effect = translator msg = _message.Message(message_with_params) msg_param = _message.Message(message_param) msg = msg % {'param': msg_param} default_translation = message_with_params % {'param': message_param} expected_translation = es_translation % {'param': es_param_translation} self.assertEqual(expected_translation, msg.translation('es')) self.assertEqual(default_translation, msg.translation('XX')) @mock.patch('locale.getdefaultlocale') @mock.patch('gettext.translation') def test_translate_message_non_default_locale(self, mock_translation, mock_getdefaultlocale): message_with_params = 'A message with params: %(param)s' es_translation = 'A message with params in Spanish: %(param)s' zh_translation = 'A message with params in Chinese: %(param)s' fr_translation = 'A message with params in French: %(param)s' message_param = 'A Message param' es_param_translation = 'A message param in Spanish' zh_param_translation = 'A message param in Chinese' fr_param_translation = 'A message param in French' es_translations = {message_with_params: es_translation, message_param: es_param_translation} zh_translations = {message_with_params: zh_translation, message_param: zh_param_translation} fr_translations = {message_with_params: fr_translation, message_param: fr_param_translation} translator = fakes.FakeTranslations.translator({'es': es_translations, 'zh': zh_translations, 'fr': fr_translations}) mock_translation.side_effect = translator mock_getdefaultlocale.return_value = ('es',) msg = _message.Message(message_with_params) msg_param = _message.Message(message_param) msg = msg % {'param': msg_param} es_translation = es_translation % {'param': es_param_translation} zh_translation = zh_translation % {'param': zh_param_translation} fr_translation = fr_translation % {'param': fr_param_translation} # Because sys.getdefaultlocale() was Spanish, # the default translation will be to Spanish self.assertEqual(es_translation, msg) self.assertEqual(es_translation, msg.translation()) self.assertEqual(es_translation, msg.translation('es')) # Translation into other locales still works self.assertEqual(zh_translation, msg.translation('zh')) self.assertEqual(fr_translation, msg.translation('fr')) # TODO(bnemec): Remove these three tests when the translate compatibility # shim is removed. def test_translate_with_dict(self): msg = _message.Message('abc') # This dict is what you get back from str.maketrans('abc', 'xyz') # We can't actually call that here because it doesn't exist on py2 # and the string.maketrans that does behaves differently. self.assertEqual('xyz', msg.translate({97: 120, 98: 121, 99: 122})) def test_translate_with_list(self): msg = _message.Message('abc') table = [six.unichr(x) for x in range(128)] table[ord('a')] = 'b' table[ord('b')] = 'c' table[ord('c')] = 'd' self.assertEqual('bcd', msg.translate(table)) @mock.patch('warnings.warn') def test_translate_warning(self, mock_warn): msg = _message.Message('a message') msg.translate('es') self.assertTrue(mock_warn.called, 'No warning found') # Make sure it was our warning self.assertIn('Message.translate called with a string argument.', mock_warn.call_args[0][0]) class TranslateMsgidTest(test_base.BaseTestCase): @mock.patch('gettext.translation') def test_contextual(self, translation): lang = mock.Mock() translation.return_value = lang trans = mock.Mock() trans.return_value = 'translated' lang.gettext = trans lang.ugettext = trans result = _message.Message._translate_msgid( ('context', 'message'), domain='domain', has_contextual_form=True, has_plural_form=False, ) self.assertEqual('translated', result) trans.assert_called_with( 'context' + _message.CONTEXT_SEPARATOR + 'message' ) @mock.patch('gettext.translation') def test_contextual_untranslatable(self, translation): msg_with_context = 'context' + _message.CONTEXT_SEPARATOR + 'message' lang = mock.Mock() translation.return_value = lang trans = mock.Mock() trans.return_value = msg_with_context lang.gettext = trans lang.ugettext = trans result = _message.Message._translate_msgid( ('context', 'message'), domain='domain', has_contextual_form=True, has_plural_form=False, ) self.assertEqual('message', result) trans.assert_called_with(msg_with_context) @mock.patch('gettext.translation') def test_plural(self, translation): lang = mock.Mock() translation.return_value = lang trans = mock.Mock() trans.return_value = 'translated' lang.ngettext = trans lang.ungettext = trans result = _message.Message._translate_msgid( ('single', 'plural', -1), domain='domain', has_contextual_form=False, has_plural_form=True, ) self.assertEqual('translated', result) trans.assert_called_with( 'single', 'plural', -1, ) @mock.patch('gettext.translation') def test_contextual_and_plural(self, translation): self.assertRaises( ValueError, _message.Message._translate_msgid, 'nothing', domain='domain', has_contextual_form=True, has_plural_form=True, ) oslo.i18n-4.0.1/oslo_i18n/tests/test_lazy.py0000664000175000017500000000233213643050246020640 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslotest import base as test_base from oslo_i18n import _lazy class LazyTest(test_base.BaseTestCase): def setUp(self): super(LazyTest, self).setUp() self._USE_LAZY = _lazy.USE_LAZY def tearDown(self): _lazy.USE_LAZY = self._USE_LAZY super(LazyTest, self).tearDown() def test_enable_lazy(self): _lazy.USE_LAZY = False _lazy.enable_lazy() self.assertTrue(_lazy.USE_LAZY) def test_disable_lazy(self): _lazy.USE_LAZY = True _lazy.enable_lazy(False) self.assertFalse(_lazy.USE_LAZY) oslo.i18n-4.0.1/oslo_i18n/tests/test_handler.py0000664000175000017500000001037313643050246021302 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from unittest import mock from oslotest import base as test_base import six from oslo_i18n import _message from oslo_i18n import log as i18n_log from oslo_i18n.tests import fakes LOG = logging.getLogger(__name__) class TranslationHandlerTestCase(test_base.BaseTestCase): def setUp(self): super(TranslationHandlerTestCase, self).setUp() self.stream = six.StringIO() self.destination_handler = logging.StreamHandler(self.stream) self.translation_handler = i18n_log.TranslationHandler('zh_CN') self.translation_handler.setTarget(self.destination_handler) self.logger = logging.getLogger('localehander_logger') self.logger.setLevel(logging.DEBUG) self.logger.addHandler(self.translation_handler) def test_set_formatter(self): formatter = 'some formatter' self.translation_handler.setFormatter(formatter) self.assertEqual(formatter, self.translation_handler.target.formatter) @mock.patch('gettext.translation') def test_emit_translated_message(self, mock_translation): log_message = 'A message to be logged' log_message_translation = 'A message to be logged in Chinese' translations = {log_message: log_message_translation} translations_map = {'zh_CN': translations} translator = fakes.FakeTranslations.translator(translations_map) mock_translation.side_effect = translator msg = _message.Message(log_message) self.logger.info(msg) self.assertIn(log_message_translation, self.stream.getvalue()) @mock.patch('gettext.translation') def test_emit_translated_message_with_args(self, mock_translation): log_message = 'A message to be logged %s' log_message_translation = 'A message to be logged in Chinese %s' log_arg = 'Arg to be logged' log_arg_translation = 'An arg to be logged in Chinese' translations = {log_message: log_message_translation, log_arg: log_arg_translation} translations_map = {'zh_CN': translations} translator = fakes.FakeTranslations.translator(translations_map) mock_translation.side_effect = translator msg = _message.Message(log_message) arg = _message.Message(log_arg) self.logger.info(msg, arg) self.assertIn(log_message_translation % log_arg_translation, self.stream.getvalue()) @mock.patch('gettext.translation') def test_emit_translated_message_with_named_args(self, mock_translation): log_message = 'A message to be logged %(arg1)s $(arg2)s' log_message_translation = 'Chinese msg to be logged %(arg1)s $(arg2)s' log_arg_1 = 'Arg1 to be logged' log_arg_1_translation = 'Arg1 to be logged in Chinese' log_arg_2 = 'Arg2 to be logged' log_arg_2_translation = 'Arg2 to be logged in Chinese' translations = {log_message: log_message_translation, log_arg_1: log_arg_1_translation, log_arg_2: log_arg_2_translation} translations_map = {'zh_CN': translations} translator = fakes.FakeTranslations.translator(translations_map) mock_translation.side_effect = translator msg = _message.Message(log_message) arg_1 = _message.Message(log_arg_1) arg_2 = _message.Message(log_arg_2) self.logger.info(msg, {'arg1': arg_1, 'arg2': arg_2}) translation = log_message_translation % {'arg1': log_arg_1_translation, 'arg2': log_arg_2_translation} self.assertIn(translation, self.stream.getvalue()) oslo.i18n-4.0.1/oslo_i18n/tests/test_fixture.py0000664000175000017500000001026413643050246021352 0ustar zuulzuul00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslotest import base as test_base import six import oslo_i18n from oslo_i18n import _gettextutils from oslo_i18n._i18n import _ from oslo_i18n import _lazy from oslo_i18n import _message from oslo_i18n import _translate from oslo_i18n import fixture class TranslationFixtureTest(test_base.BaseTestCase): def setUp(self): super(TranslationFixtureTest, self).setUp() self.trans_fixture = self.useFixture(fixture.Translation()) def test_lazy(self): msg = self.trans_fixture.lazy('this is a lazy message') self.assertIsInstance(msg, _message.Message) self.assertEqual('this is a lazy message', msg.msgid) def test_immediate(self): msg = self.trans_fixture.immediate('this is a lazy message') self.assertNotIsInstance(msg, _message.Message) self.assertIsInstance(msg, six.text_type) self.assertEqual(u'this is a lazy message', msg) class ToggleLazyFixtureText(test_base.BaseTestCase): def test_on_on(self): _lazy.USE_LAZY = True f = fixture.ToggleLazy(True) f.setUp() self.assertTrue(_lazy.USE_LAZY) f._restore_original() self.assertTrue(_lazy.USE_LAZY) def test_on_off(self): _lazy.USE_LAZY = True f = fixture.ToggleLazy(False) f.setUp() self.assertFalse(_lazy.USE_LAZY) f._restore_original() self.assertTrue(_lazy.USE_LAZY) def test_off_on(self): _lazy.USE_LAZY = False f = fixture.ToggleLazy(True) f.setUp() self.assertTrue(_lazy.USE_LAZY) f._restore_original() self.assertFalse(_lazy.USE_LAZY) def test_off_off(self): _lazy.USE_LAZY = False f = fixture.ToggleLazy(False) f.setUp() self.assertFalse(_lazy.USE_LAZY) f._restore_original() self.assertFalse(_lazy.USE_LAZY) _FAKE_LANG = 'en_ZZ' class PrefixLazyTranslationTest(test_base.BaseTestCase): def test_default(self): # Turn lazy off to check that fixture turns it on self.useFixture(fixture.ToggleLazy(False)) self.useFixture(fixture.PrefixLazyTranslation()) self.assertTrue(_lazy.USE_LAZY) default_lang = fixture.PrefixLazyTranslation._DEFAULT_LANG raw_id1 = 'fake msg1' expected_msg = 'oslo_i18n/' + default_lang + ': ' + raw_id1 msg1 = _(raw_id1) # noqa self.assertEqual([default_lang], _gettextutils.get_available_languages('oslo_i18n')) self.assertEqual([default_lang], oslo_i18n.get_available_languages('oslo_i18n')) self.assertEqual(expected_msg, _translate.translate(msg1)) def test_extra_lang(self): languages = _gettextutils.get_available_languages('oslo') languages.append(_FAKE_LANG) self.useFixture(fixture.PrefixLazyTranslation(languages=languages)) raw_id1 = 'fake msg1' expected_msg_en_US = ('oslo_i18n/' + fixture.PrefixLazyTranslation._DEFAULT_LANG + ': ' + raw_id1) expected_msg_en_ZZ = 'oslo_i18n/' + _FAKE_LANG + ': ' + raw_id1 msg1 = _(raw_id1) # noqa self.assertEqual(languages, _gettextutils.get_available_languages('oslo_i18n')) self.assertEqual(languages, oslo_i18n.get_available_languages('oslo_i18n')) self.assertEqual(expected_msg_en_US, _translate.translate(msg1)) self.assertEqual(expected_msg_en_ZZ, _translate.translate(msg1, desired_locale=_FAKE_LANG)) oslo.i18n-4.0.1/oslo_i18n/tests/test_factory.py0000664000175000017500000001036413643050246021334 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from oslotest import base as test_base import six from oslo_i18n import _factory from oslo_i18n import _lazy from oslo_i18n import _message # magic gettext number to separate context from message CONTEXT_SEPARATOR = _message.CONTEXT_SEPARATOR class TranslatorFactoryTest(test_base.BaseTestCase): def setUp(self): super(TranslatorFactoryTest, self).setUp() # remember so we can reset to it later in case it changes self._USE_LAZY = _lazy.USE_LAZY def tearDown(self): # reset to value before test _lazy.USE_LAZY = self._USE_LAZY super(TranslatorFactoryTest, self).tearDown() def test_lazy(self): _lazy.enable_lazy(True) with mock.patch.object(_message, 'Message') as msg: tf = _factory.TranslatorFactory('domain') tf.primary('some text') msg.assert_called_with('some text', domain='domain') def test_not_lazy(self): _lazy.enable_lazy(False) with mock.patch.object(_message, 'Message') as msg: msg.side_effect = AssertionError('should not use Message') tf = _factory.TranslatorFactory('domain') tf.primary('some text') def test_change_lazy(self): _lazy.enable_lazy(True) tf = _factory.TranslatorFactory('domain') r = tf.primary('some text') self.assertIsInstance(r, _message.Message) _lazy.enable_lazy(False) r = tf.primary('some text') self.assertNotIsInstance(r, _message.Message) def test_py3(self): _lazy.enable_lazy(False) with mock.patch.object(six, 'PY3', True): with mock.patch('gettext.translation') as translation: trans = mock.Mock() translation.return_value = trans trans.ugettext.side_effect = AssertionError( 'should have called gettext') tf = _factory.TranslatorFactory('domain') tf.primary('some text') trans.gettext.assert_called_with('some text') def test_log_level_domain_name(self): with mock.patch.object(_factory.TranslatorFactory, '_make_translation_func') as mtf: tf = _factory.TranslatorFactory('domain') tf._make_log_translation_func('mylevel') mtf.assert_called_with('domain-log-mylevel') def test_contextual_form_py3(self): _lazy.enable_lazy(False) with mock.patch.object(six, 'PY3', True): with mock.patch('gettext.translation') as translation: trans = mock.Mock() translation.return_value = trans trans.ugettext.side_effect = AssertionError( 'should have called gettext') trans.gettext.return_value = "some text" tf = _factory.TranslatorFactory('domain') tf.contextual_form('context', 'some text') trans.gettext.assert_called_with( "%s%s%s" % ('context', CONTEXT_SEPARATOR, 'some text')) def test_plural_form_py3(self): _lazy.enable_lazy(False) with mock.patch.object(six, 'PY3', True): with mock.patch('gettext.translation') as translation: trans = mock.Mock() translation.return_value = trans trans.ungettext.side_effect = AssertionError( 'should have called ngettext') tf = _factory.TranslatorFactory('domain') tf.plural_form('single', 'plural', 1) trans.ngettext.assert_called_with( 'single', 'plural', 1) oslo.i18n-4.0.1/oslo_i18n/tests/utils.py0000664000175000017500000000220113643050246017755 0ustar zuulzuul00000000000000# All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import six class SomeObject(object): def __init__(self, message): self.message = message def __unicode__(self): return self.message # alias for Python 3 __str__ = __unicode__ class NoDeepCopyObject(object): def __init__(self, value): self.value = value if six.PY3: def __str__(self): return str(self.value) else: def __unicode__(self): return six.text_type(self.value) def __deepcopy__(self, memo): raise TypeError('Deep Copy not supported') oslo.i18n-4.0.1/oslo_i18n/tests/test_gettextutils.py0000664000175000017500000001361013643050246022427 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import gettext import logging from unittest import mock from babel import localedata from oslotest import base as test_base import six from oslo_i18n import _factory from oslo_i18n import _gettextutils from oslo_i18n import _lazy from oslo_i18n import _message LOG = logging.getLogger(__name__) class GettextTest(test_base.BaseTestCase): def setUp(self): super(GettextTest, self).setUp() # remember so we can reset to it later in case it changes self._USE_LAZY = _lazy.USE_LAZY self.t = _factory.TranslatorFactory('oslo_i18n.test') def tearDown(self): # reset to value before test _lazy.USE_LAZY = self._USE_LAZY super(GettextTest, self).tearDown() def test_gettext_does_not_blow_up(self): LOG.info(self.t.primary('test')) def test__gettextutils_install(self): _gettextutils.install('blaa') _lazy.enable_lazy(False) self.assertTrue(isinstance(self.t.primary('A String'), six.text_type)) _gettextutils.install('blaa') _lazy.enable_lazy(True) self.assertTrue(isinstance(self.t.primary('A Message'), _message.Message)) def test_gettext_install_looks_up_localedir(self): with mock.patch('os.environ.get') as environ_get: with mock.patch('gettext.install'): environ_get.return_value = '/foo/bar' _gettextutils.install('blaa') environ_get.assert_has_calls([mock.call('BLAA_LOCALEDIR')]) def test_gettext_install_updates_builtins(self): with mock.patch('os.environ.get') as environ_get: with mock.patch('gettext.install'): environ_get.return_value = '/foo/bar' if '_' in six.moves.builtins.__dict__: del six.moves.builtins.__dict__['_'] _gettextutils.install('blaa') self.assertIn('_', six.moves.builtins.__dict__) def test_get_available_languages(self): # All the available languages for which locale data is available def _mock_locale_identifiers(): # 'zh', 'zh_Hant'. 'zh_Hant_HK', 'fil' all have aliases # missing from babel but we add them in _gettextutils, we # test that here too return ['zh', 'es', 'nl', 'fr', 'zh_Hant', 'zh_Hant_HK', 'fil'] mock_patcher = mock.patch.object(localedata, 'list' if hasattr(localedata, 'list') else 'locale_identifiers', _mock_locale_identifiers) mock_patcher.start() self.addCleanup(mock_patcher.stop) # Only the languages available for a specific translation domain def _mock_gettext_find(domain, localedir=None, languages=None, all=0): languages = languages or [] if domain == 'domain_1': return 'translation-file' if any(x in ['zh', 'es', 'fil'] for x in languages) else None elif domain == 'domain_2': return 'translation-file' if any(x in ['fr', 'zh_Hant'] for x in languages) else None return None mock_patcher = mock.patch.object(gettext, 'find', _mock_gettext_find) mock_patcher.start() self.addCleanup(mock_patcher.stop) # Ensure that no domains are cached _gettextutils._AVAILABLE_LANGUAGES = {} # en_US should always be available no matter the domain # and it should also always be the first element since order matters domain_1_languages = _gettextutils.get_available_languages('domain_1') domain_2_languages = _gettextutils.get_available_languages('domain_2') self.assertEqual('en_US', domain_1_languages[0]) self.assertEqual('en_US', domain_2_languages[0]) # The domain languages should be included after en_US with # with their respective aliases when it applies self.assertEqual(6, len(domain_1_languages)) self.assertIn('zh', domain_1_languages) self.assertIn('zh_CN', domain_1_languages) self.assertIn('es', domain_1_languages) self.assertIn('fil', domain_1_languages) self.assertIn('tl_PH', domain_1_languages) self.assertEqual(4, len(domain_2_languages)) self.assertIn('fr', domain_2_languages) self.assertIn('zh_Hant', domain_2_languages) self.assertIn('zh_TW', domain_2_languages) self.assertEqual(2, len(_gettextutils._AVAILABLE_LANGUAGES)) # Now test an unknown domain, only en_US should be included unknown_domain_languages = _gettextutils.get_available_languages('huh') self.assertEqual(1, len(unknown_domain_languages)) self.assertIn('en_US', unknown_domain_languages) def test_cached_find(self): domain = 'my-unique-domain' key = (domain, None, None, 0) self.assertNotIn(key, _gettextutils._FIND_CACHE) gettext.find(domain) self.assertIn(key, _gettextutils._FIND_CACHE) _gettextutils._FIND_CACHE[key] = "spoof result" self.assertEqual("spoof result", gettext.find(domain)) _gettextutils._FIND_CACHE.pop(key) oslo.i18n-4.0.1/oslo_i18n/tests/__init__.py0000664000175000017500000000000013643050246020347 0ustar zuulzuul00000000000000oslo.i18n-4.0.1/oslo_i18n/tests/test_logging.py0000664000175000017500000000250513643050246021311 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from unittest import mock from oslotest import base as test_base from oslo_i18n import _factory class LogLevelTranslationsTest(test_base.BaseTestCase): def test_info(self): self._test('info') def test_warning(self): self._test('warning') def test_error(self): self._test('error') def test_critical(self): self._test('critical') def _test(self, level): with mock.patch.object(_factory.TranslatorFactory, '_make_translation_func') as mtf: tf = _factory.TranslatorFactory('domain') getattr(tf, 'log_%s' % level) mtf.assert_called_with('domain-log-%s' % level) oslo.i18n-4.0.1/oslo_i18n/_factory.py0000664000175000017500000001571013643050246017272 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Translation function factory """ import gettext import os import six from oslo_i18n import _lazy from oslo_i18n import _locale from oslo_i18n import _message __all__ = [ 'TranslatorFactory', ] # magic gettext number to separate context from message CONTEXT_SEPARATOR = _message.CONTEXT_SEPARATOR class TranslatorFactory(object): "Create translator functions" def __init__(self, domain, localedir=None): """Establish a set of translation functions for the domain. :param domain: Name of translation domain, specifying a message catalog. :type domain: str :param localedir: Directory with translation catalogs. :type localedir: str """ self.domain = domain if localedir is None: variable_name = _locale.get_locale_dir_variable_name(domain) localedir = os.environ.get(variable_name) self.localedir = localedir def _make_translation_func(self, domain=None): """Return a translation function ready for use with messages. The returned function takes a single value, the unicode string to be translated. The return type varies depending on whether lazy translation is being done. When lazy translation is enabled, :class:`Message` objects are returned instead of regular :class:`unicode` strings. The domain argument can be specified to override the default from the factory, but the localedir from the factory is always used because we assume the log-level translation catalogs are installed in the same directory as the main application catalog. """ if domain is None: domain = self.domain t = gettext.translation(domain, localedir=self.localedir, fallback=True) # Use the appropriate method of the translation object based # on the python version. m = t.gettext if six.PY3 else t.ugettext def f(msg): """oslo_i18n.gettextutils translation function.""" if _lazy.USE_LAZY: return _message.Message(msg, domain=domain) return m(msg) return f def _make_contextual_translation_func(self, domain=None): """Return a translation function ready for use with context messages. The returned function takes two values, the context of the unicode string, the unicode string to be translated. The returned type is the same as :method:`TranslatorFactory._make_translation_func`. The domain argument is the same as :method:`TranslatorFactory._make_translation_func`. """ if domain is None: domain = self.domain t = gettext.translation(domain, localedir=self.localedir, fallback=True) # Use the appropriate method of the translation object based # on the python version. m = t.gettext if six.PY3 else t.ugettext def f(ctx, msg): """oslo.i18n.gettextutils translation with context function.""" if _lazy.USE_LAZY: msgid = (ctx, msg) return _message.Message(msgid, domain=domain, has_contextual_form=True) msgctx = "%s%s%s" % (ctx, CONTEXT_SEPARATOR, msg) s = m(msgctx) if CONTEXT_SEPARATOR in s: # Translation not found return msg return s return f def _make_plural_translation_func(self, domain=None): """Return a plural translation function ready for use with messages. The returned function takes three values, the single form of the unicode string, the plural form of the unicode string, the count of items to be translated. The returned type is the same as :method:`TranslatorFactory._make_translation_func`. The domain argument is the same as :method:`TranslatorFactory._make_translation_func`. """ if domain is None: domain = self.domain t = gettext.translation(domain, localedir=self.localedir, fallback=True) # Use the appropriate method of the translation object based # on the python version. m = t.ngettext if six.PY3 else t.ungettext def f(msgsingle, msgplural, msgcount): """oslo.i18n.gettextutils plural translation function.""" if _lazy.USE_LAZY: msgid = (msgsingle, msgplural, msgcount) return _message.Message(msgid, domain=domain, has_plural_form=True) return m(msgsingle, msgplural, msgcount) return f @property def primary(self): "The default translation function." return self._make_translation_func() @property def contextual_form(self): """The contextual translation function. The returned function takes two values, the context of the unicode string, the unicode string to be translated. .. versionadded:: 2.1.0 """ return self._make_contextual_translation_func() @property def plural_form(self): """The plural translation function. The returned function takes three values, the single form of the unicode string, the plural form of the unicode string, the count of items to be translated. .. versionadded:: 2.1.0 """ return self._make_plural_translation_func() def _make_log_translation_func(self, level): return self._make_translation_func(self.domain + '-log-' + level) @property def log_info(self): "Translate info-level log messages." return self._make_log_translation_func('info') @property def log_warning(self): "Translate warning-level log messages." return self._make_log_translation_func('warning') @property def log_error(self): "Translate error-level log messages." return self._make_log_translation_func('error') @property def log_critical(self): "Translate critical-level log messages." return self._make_log_translation_func('critical') oslo.i18n-4.0.1/oslo_i18n/__init__.py0000664000175000017500000000124113643050246017215 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from ._factory import * from ._gettextutils import * from ._lazy import * from ._translate import * oslo.i18n-4.0.1/oslo_i18n/_lazy.py0000664000175000017500000000236213643050246016601 0ustar zuulzuul00000000000000# Copyright 2012 Red Hat, Inc. # Copyright 2013 IBM Corp. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. __all__ = [ 'enable_lazy', ] USE_LAZY = False def enable_lazy(enable=True): """Convenience function for configuring _() to use lazy gettext Call this at the start of execution to enable the gettextutils._ function to use lazy gettext functionality. This is useful if your project is importing _ directly instead of using the gettextutils.install() way of importing the _ function. :param enable: Flag indicating whether lazy translation should be turned on or off. Defaults to True. :type enable: bool """ global USE_LAZY USE_LAZY = enable oslo.i18n-4.0.1/.coveragerc0000664000175000017500000000015513643050246015415 0ustar zuulzuul00000000000000[run] branch = True source = oslo_i18n omit = oslo_i18n/tests/* [report] ignore_errors = True precision = 2 oslo.i18n-4.0.1/HACKING.rst0000664000175000017500000000017013643050246015067 0ustar zuulzuul00000000000000Style Commandments ================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ oslo.i18n-4.0.1/PKG-INFO0000664000175000017500000000422013643050342014363 0ustar zuulzuul00000000000000Metadata-Version: 1.2 Name: oslo.i18n Version: 4.0.1 Summary: Oslo i18n library Home-page: https://docs.openstack.org/oslo.i18n/latest Author: OpenStack Author-email: openstack-discuss@lists.openstack.org License: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/oslo.i18n.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ================================================== oslo.i18n -- Oslo Internationalization Utilities ================================================== .. image:: https://img.shields.io/pypi/v/oslo.i18n.svg :target: https://pypi.org/project/oslo.i18n/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/oslo.i18n.svg :target: https://pypi.org/project/oslo.i18n/ :alt: Downloads The oslo.i18n library contain utilities for working with internationalization (i18n) features, especially translation for text strings in an application or library. * Free software: Apache license * Documentation: https://docs.openstack.org/oslo.i18n/latest * Source: https://opendev.org/openstack/oslo.i18n * Bugs: https://bugs.launchpad.net/oslo.i18n * Release notes: https://docs.openstack.org/releasenotes/oslo.i18n/ Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: Implementation :: CPython Requires-Python: >=3.6 oslo.i18n-4.0.1/releasenotes/0000775000175000017500000000000013643050342015761 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/releasenotes/source/0000775000175000017500000000000013643050342017261 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/releasenotes/source/conf.py0000664000175000017500000002146713643050246020575 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options repository_name = 'openstack/oslo.i18n' bug_project = 'oslo.i18n' bug_tag = '' html_last_updated_fmt = '%Y-%m-%d %H:%M' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'oslo.i18n Release Notes' copyright = u'2016, oslo.i18n Developers' # Release notes do not need a version in the title, they span # multiple versions. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all # documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'oslo.i18nReleaseNotesDoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'oslo.i18nReleaseNotes.tex', u'oslo.i18n Release Notes Documentation', u'oslo.i18n Developers', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'oslo.i18nReleaseNotes', u'oslo.i18n Release Notes Documentation', [u'oslo.i18n Developers'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'oslo.i18nReleaseNotes', u'oslo.i18n Release Notes Documentation', u'oslo.i18n Developers', 'oslo.i18nReleaseNotes', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] oslo.i18n-4.0.1/releasenotes/source/train.rst0000664000175000017500000000017613643050246021137 0ustar zuulzuul00000000000000========================== Train Series Release Notes ========================== .. release-notes:: :branch: stable/train oslo.i18n-4.0.1/releasenotes/source/rocky.rst0000664000175000017500000000022113643050246021140 0ustar zuulzuul00000000000000=================================== Rocky Series Release Notes =================================== .. release-notes:: :branch: stable/rocky oslo.i18n-4.0.1/releasenotes/source/index.rst0000664000175000017500000000027113643050246021125 0ustar zuulzuul00000000000000========================= oslo.i18n Release Notes ========================= .. toctree:: :maxdepth: 1 unreleased train stein rocky queens pike ocata oslo.i18n-4.0.1/releasenotes/source/locale/0000775000175000017500000000000013643050342020520 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/releasenotes/source/locale/en_GB/0000775000175000017500000000000013643050342021472 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/releasenotes/source/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000013643050342023257 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po0000664000175000017500000000251713643050246026320 0ustar zuulzuul00000000000000# Andi Chandler , 2017. #zanata # Andi Chandler , 2018. #zanata # Andi Chandler , 2019. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.i18n\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2019-11-06 04:11+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2019-12-21 02:55+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "3.11.0" msgstr "3.11.0" msgid "Introduce reno for deployer release notes." msgstr "Introduce reno for deployer release notes." msgid "Ocata Series Release Notes" msgstr "Ocata Series Release Notes" msgid "Other Notes" msgstr "Other Notes" msgid "Pike Series Release Notes" msgstr "Pike Series Release Notes" msgid "Queens Series Release Notes" msgstr "Queens Series Release Notes" msgid "Rocky Series Release Notes" msgstr "Rocky Series Release Notes" msgid "Stein Series Release Notes" msgstr "Stein Series Release Notes" msgid "Train Series Release Notes" msgstr "Train Series Release Notes" msgid "Unreleased Release Notes" msgstr "Unreleased Release Notes" msgid "oslo.i18n Release Notes" msgstr "oslo.i18n Release Notes" oslo.i18n-4.0.1/releasenotes/source/locale/ko_KR/0000775000175000017500000000000013643050342021525 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/releasenotes/source/locale/ko_KR/LC_MESSAGES/0000775000175000017500000000000013643050342023312 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/releasenotes/source/locale/ko_KR/LC_MESSAGES/releasenotes.po0000664000175000017500000000201513643050246026344 0ustar zuulzuul00000000000000# minwook-shin , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.i18n Release Notes\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-02-05 13:14+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-08-04 07:11+0000\n" "Last-Translator: minwook-shin \n" "Language-Team: Korean (South Korea)\n" "Language: ko_KR\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=1; plural=0\n" msgid "3.11.0" msgstr "3.11.0" msgid "Introduce reno for deployer release notes." msgstr "릴리즈 노트 배포에 reno를 도입하기." msgid "Ocata Series Release Notes" msgstr "Ocata 시리즈에 대한 릴리즈 노트" msgid "Other Notes" msgstr "기타 노트" msgid "Pike Series Release Notes" msgstr "Pike 시리즈에 대한 릴리즈 노트" msgid "Unreleased Release Notes" msgstr "릴리즈되지않은 릴리즈 노트" msgid "oslo.i18n Release Notes" msgstr "oslo.i18n 릴리즈 노트" oslo.i18n-4.0.1/releasenotes/source/ocata.rst0000664000175000017500000000023013643050246021100 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata oslo.i18n-4.0.1/releasenotes/source/_static/0000775000175000017500000000000013643050342020707 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/releasenotes/source/_static/.placeholder0000664000175000017500000000000013643050246023163 0ustar zuulzuul00000000000000oslo.i18n-4.0.1/releasenotes/source/pike.rst0000664000175000017500000000021713643050246020746 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike oslo.i18n-4.0.1/releasenotes/source/queens.rst0000664000175000017500000000022313643050246021313 0ustar zuulzuul00000000000000=================================== Queens Series Release Notes =================================== .. release-notes:: :branch: stable/queens oslo.i18n-4.0.1/releasenotes/source/stein.rst0000664000175000017500000000022113643050246021133 0ustar zuulzuul00000000000000=================================== Stein Series Release Notes =================================== .. release-notes:: :branch: stable/stein oslo.i18n-4.0.1/releasenotes/source/_templates/0000775000175000017500000000000013643050342021416 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/releasenotes/source/_templates/.placeholder0000664000175000017500000000000013643050246023672 0ustar zuulzuul00000000000000oslo.i18n-4.0.1/releasenotes/source/unreleased.rst0000664000175000017500000000014413643050246022144 0ustar zuulzuul00000000000000========================== Unreleased Release Notes ========================== .. release-notes:: oslo.i18n-4.0.1/releasenotes/notes/0000775000175000017500000000000013643050342017111 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/releasenotes/notes/add-reno-996dd44974d53238.yaml0000664000175000017500000000007213643050246023512 0ustar zuulzuul00000000000000--- other: - Introduce reno for deployer release notes. oslo.i18n-4.0.1/releasenotes/notes/drop-python27-support-eb9c2ad3268f0c1f.yaml0000664000175000017500000000017713643050246026525 0ustar zuulzuul00000000000000--- upgrade: - | Support for Python 2.7 has been dropped. The minimum version of Python now supported is Python 3.6. oslo.i18n-4.0.1/lower-constraints.txt0000664000175000017500000000152613643050246017535 0ustar zuulzuul00000000000000alabaster==0.7.10 appdirs==1.3.0 Babel==2.3.4 bandit==1.4.0 coverage==4.0 debtcollector==1.2.0 docutils==0.11 dulwich==0.15.0 extras==1.0.0 fixtures==3.0.0 flake8==2.5.5 gitdb==0.6.4 GitPython==1.0.1 hacking==0.12.0 imagesize==0.7.1 iso8601==0.1.11 Jinja2==2.10 keystoneauth1==3.4.0 linecache2==1.0.0 MarkupSafe==1.0 mccabe==0.2.1 mox3==0.20.0 netaddr==0.7.18 openstackdocstheme==1.18.1 os-client-config==1.28.0 oslo.config==5.2.0 oslotest==3.2.0 pbr==2.0.0 pep8==1.5.7 pyflakes==0.8.1 Pygments==2.2.0 python-mimeparse==1.6.0 python-subunit==1.0.0 pytz==2013.6 PyYAML==3.12 reno==2.5.0 requests==2.14.2 requestsexceptions==1.2.0 rfc3986==0.3.1 six==1.10.0 smmap==0.9.0 snowballstemmer==1.2.1 Sphinx==1.8.0 sphinxcontrib-websupport==1.0.1 stestr==2.0.0 stevedore==1.20.0 testscenarios==0.4 testtools==2.2.0 traceback2==1.4.0 unittest2==1.1.0 wrapt==1.7.0 oslo.i18n-4.0.1/setup.cfg0000664000175000017500000000212613643050342015112 0ustar zuulzuul00000000000000[metadata] name = oslo.i18n summary = Oslo i18n library description-file = README.rst author = OpenStack author-email = openstack-discuss@lists.openstack.org home-page = https://docs.openstack.org/oslo.i18n/latest python-requires = >=3.6 classifier = Environment :: OpenStack Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: Implementation :: CPython [files] packages = oslo_i18n [compile_catalog] directory = oslo_i18n/locale domain = oslo_i18n [update_catalog] domain = oslo_i18n output_dir = oslo_i18n/locale input_file = oslo_i18n/locale/oslo_i18n.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext _C:1c,2 _P:1,2 mapping_file = babel.cfg output_file = oslo_i18n/locale/oslo_i18n.pot [egg_info] tag_build = tag_date = 0 oslo.i18n-4.0.1/AUTHORS0000664000175000017500000000602713643050342014345 0ustar zuulzuul00000000000000Adam Spiers Akihiro Motoki Akihiro Motoki Alessio Ababilov Anderson Mesquita Andreas Jaeger Andreas Jaeger Andrey Kurilin Balazs Gibizer Ben Nemec Ben Nemec Bo Chi Brant Knudson Chang Bo Guo ChangBo Guo(gcb) Charles Short Charles Short Christian Berendt Chuck Short Chuck Short Corey Bryant Dan Prince Davanum Srinivas Davanum Srinivas David Stanek Dina Belova Doug Hellmann Doug Hellmann Flavio Percoco Ghanshyam Mann Hervé Beraud James Carey Jason Kölker Jay Pipes Jeremy Stanley Joe Gordon Joe Heck John Warren Joshua Harlow Joshua Harlow Juan Antonio Osorio Julien Danjou Kenneth Giusti Luis A. Garcia Luong Anh Tuan Mark McLoughlin Mathew Odden Matt Odden Monty Taylor Nikhil Manchanda Noorul Islam K M OpenStack Release Bot Peng Wu Rajaram Mallya Ronald Bradford Rosario Di Somma Russell Bryant Sean McGinnis Sean McGinnis Sergey Kraynev Stephen Finucane Steve Martinelli Thomas Herve Tony Breeds Victor Sergeyev Victor Stinner Vu Cong Tuan WangBinbin ZhiQiang Fan ZhijunWei Zhiteng Huang ZhongShengping Zhongyue Luo caoyuan howardlee jacky06 kavithahr lingyongxu malei melissaml ricolin tonybrad venkatamahesh yanheven oslo.i18n-4.0.1/test-requirements.txt0000664000175000017500000000075413643050246017542 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking>=3.0,<3.1.0 # Apache-2.0 stestr>=2.0.0 # Apache-2.0 oslotest>=3.2.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 testscenarios>=0.4 # Apache-2.0/BSD # for pre-release tests oslo.config>=5.2.0 # Apache-2.0 # Bandit security code scanner bandit>=1.1.0,<1.6.0 # Apache-2.0 oslo.i18n-4.0.1/.stestr.conf0000664000175000017500000000006213643050246015542 0ustar zuulzuul00000000000000[DEFAULT] test_path=./oslo_i18n/tests top_path=./ oslo.i18n-4.0.1/README.rst0000664000175000017500000000210113643050246014754 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: https://governance.openstack.org/tc/badges/oslo.i18n.svg :target: https://governance.openstack.org/tc/reference/tags/index.html .. Change things from this point on ================================================== oslo.i18n -- Oslo Internationalization Utilities ================================================== .. image:: https://img.shields.io/pypi/v/oslo.i18n.svg :target: https://pypi.org/project/oslo.i18n/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/oslo.i18n.svg :target: https://pypi.org/project/oslo.i18n/ :alt: Downloads The oslo.i18n library contain utilities for working with internationalization (i18n) features, especially translation for text strings in an application or library. * Free software: Apache license * Documentation: https://docs.openstack.org/oslo.i18n/latest * Source: https://opendev.org/openstack/oslo.i18n * Bugs: https://bugs.launchpad.net/oslo.i18n * Release notes: https://docs.openstack.org/releasenotes/oslo.i18n/ oslo.i18n-4.0.1/ChangeLog0000664000175000017500000003075413643050342015053 0ustar zuulzuul00000000000000CHANGES ======= 4.0.1 ----- * Use unittest.mock instead of third party mock * Update hacking for Python3 4.0.0 ----- * remove outdated header * [ussuri][goal] Drop python 2.7 support and testing * tox: Trivial cleanup 3.25.1 ------ * Imported Translations from Zanata * Integrate sphinxcontrib-apidoc 3.25.0 ------ * tox: Keeping going with docs * Update master for stable/train * Deprecate Message.translate in favor of Message.translation * Allow Message.translate to handle unhashable inputs 3.24.0 ------ * Add Python 3 Train unit tests * Move doc related modules to doc/requirements.txt * Clarify that translation strings are extracted via source inspection * Fix guidelines w.r.t. translation of log messages * Move to releases.openstack.org * Cap Bandit below 1.6.0 and update Sphinx requirement * Replace git.openstack.org URLs with opendev.org URLs * OpenDev Migration Patch * Dropping the py35 testing * Update master for stable/stein 3.23.1 ------ * add python 3.7 unit test job * Change python3.5 job to python3.7 job on Stein+ * Update hacking version * Update mailinglist from dev to discuss 3.23.0 ------ * Override getttext.find to cache result * Don't quote {posargs} in tox.ini * Clean up .gitignore references to personal tools * Always build universal wheels * Remove references to log translation functions * Use templates for cover and lower-constraints 3.22.1 ------ * Remove unused code * Imported Translations from Zanata * add lib-forward-testing-python3 test job * add python 3.6 unit test job * import zuul job settings from project-config * Update reno for stable/rocky 3.21.0 ------ * Switch to stestr * Add release notes to README.rst * fix tox python3 overrides * Remove moxstubout usage * Remove stale pip-missing-reqs tox test * Trivial: Update pypi url to new url * set default python to python3 * add lower-constraints job * Updated from global requirements 3.20.0 ------ * Imported Translations from Zanata * Update links in README * Imported Translations from Zanata * Update reno for stable/queens * Updated from global requirements * Updated from global requirements * Updated from global requirements 3.19.0 ------ * Remove -U from pip install * Avoid tox\_install.sh for constraints support * add bandit to pep8 job * Updated from global requirements * Remove setting of version/release from releasenotes * Updated from global requirements * Imported Translations from Zanata * Updated from global requirements 3.18.0 ------ * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata * Update reno for stable/pike * Updated from global requirements 3.17.0 ------ * Imported Translations from Zanata * Update URLs in documents according to document migration 3.16.0 ------ * switch from oslosphinx to openstackdocstheme * turn on warning-is-error in doc build * rearrange the documentation to fit into the new standard layout * Updated from global requirements * Enable some off-by-default checks * Updated from global requirements * Updated from global requirements * Updated from global requirements 3.15.3 ------ * Revert "Remove Babel as a runtime requirement" 3.15.2 ------ * Updated from global requirements * Remove Babel as a runtime requirement 3.15.1 ------ * Updated from global requirements * Updated from global requirements * Updated from global requirements * Check reStructuredText documents for common style issues 3.15.0 ------ * add notes about skipping log translation setup * Updated from global requirements 3.14.0 ------ * Python 3.5 is added 3.13.0 ------ * Updated from global requirements * [Fix gate]Update test requirement * Fix wrong response with language zh-TW * Updated from global requirements * Update reno for stable/ocata 3.12.0 ------ * Add Constraints support * Show team and repo badges on README * Replace six.iteritems() with .items() 3.11.0 ------ * Updated from global requirements * Updated from global requirements * Updated from global requirements * Add reno for release notes management * Add missing dependency testscenarios * Typo fix in oslo.i18n * Changed the home-page link * Add docs on how to display translated strings in your app 3.10.0 ------ * Updated from global requirements 3.9.0 ----- * Updated from global requirements * Fix parameters of assertEqual are misplaced 3.8.0 ----- * Updated from global requirements * Don't include openstack/common in flake8 exclude list * Updated from global requirements 3.7.0 ----- * Imported Translations from Zanata * Updated from global requirements 3.6.0 ----- * Updated from global requirements * Updated from global requirements * Better isolate tests and fixtures from environment * Updated from global requirements 3.4.0 ----- * Imported Translations from Zanata 3.3.0 ----- * Update translation setup * Updated from global requirements * Imported Translations from Zanata * Updated from global requirements 3.2.0 ----- * Updated from global requirements * add versionadded designations to newer functions * doc: contextual/plural translation requires oslo.i18n >=2.1.0 * Trival: Remove 'MANIFEST.in' 3.1.0 ----- * [doc] Update \_i18n.py example to pass pep8 * Add missing blank in usage.rst * Remove Python 2.6 workround for logging * Drop python 2.6 support 3.0.0 ----- * Updated from global requirements * Remove python 2.6 classifier * Remove python 2.6 and cleanup tox.ini * Improved integration module documentation * Updated from global requirements * Imported Translations from Zanata 2.7.0 ----- * Fix coverage configuration and execution * No need for Oslo Incubator Sync * Enhance the formatting error robustness patch * Imported Translations from Zanata * Move 'history' -> release notes section * Add shields.io version/downloads links/badges into README.rst * Change ignore-errors to ignore\_errors * Added the home-page value with Oslo wiki * Updated from global requirements 2.6.0 ----- * Updated from global requirements * Updated from global requirements 2.5.0 ----- * Trap formatting errors 2.4.0 ----- * Imported Translations from Transifex * Updated from global requirements * Imported Translations from Transifex * Updated from global requirements * Clean up \_translate\_msgid logic a bit 2.3.0 ----- * Imported Translations from Transifex * Updated from global requirements 2.2.0 ----- * Imported Translations from Transifex * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix mock use for 1.1.0 * Add requirements for pre-release test scripts * Imported Translations from Transifex 2.1.0 ----- * Only define CONTEXT\_SEPARATOR once * Support contextual and plural form of gettext functions * Imported Translations from Transifex * clarify translation policy * Add tox target to find missing requirements * Imported Translations from Transifex 2.0.0 ----- * Updated from global requirements * Updated from global requirements * Remove oslo namespace package 1.7.0 ----- * Advertise support for Python3.4 / Remove support for Python 3.3 * Updated from global requirements * Misplaced parenthesis causing confusion * Remove run\_cross\_tests.sh * Imported Translations from Transifex 1.6.0 ----- * Uncap library requirements for liberty * Standardize setup.cfg summary for oslo libs * Updated from global requirements * Move to hacking 0.10 * Update guideline doc of multiple use msg case 1.5.0 ----- * Updated from global requirements 1.4.0 ----- * Add test fixture to prefix lazily translated messages 1.3.1 ----- * Clear global cache in test\_get\_available\_languages * Make setup.cfg packages include oslo.i18n * Improve fixture documentation 1.3.0 ----- * Add a fixture to let tests manage the lazy flag * Fix the link to the bug tracker in the README 1.2.0 ----- * Correct the translation domain for loading messages * Move out of the oslo namespace package * Workflow documentation is now in infra-manual * Force code sample to be treated as python 1.1.0 ----- * Imported Translations from Transifex * Add note for integration modules in libraries * Activate pep8 check that \_ is imported * Add pbr to installation requirements * Updated from global requirements * Updated from global requirements * Remove extraneous vim editor configuration comments * Make clear in docs to use \_LE() when using LOG.exception() * Support building wheels (PEP-427) * Imported Translations from Transifex * Fix coverage testing * Imported Translations from Transifex * Use same indentation in doc/source/usage * Imported Translations from Transifex * Imported Translations from Transifex * Updated from global requirements * Remove unused/mutable default args * Fixes a small syntax error in the doc examples 1.0.0 ----- * Add missing six dependency 0.3.0 ----- * Imported Translations from Transifex * Work toward Python 3.4 support and testing * Updated from global requirements * Imported Translations from Transifex * Document how to add import exceptions 0.2.0 ----- * Add a test fixture for translatable strings * Imported Translations from Transifex * Remove mention of Message objects from public docs * Add Changelog to the documentation 0.1.0 ----- * Shift public API to top level package * Add links to policy discussions * Improve initial documentation * Update sphinx and hacking requirements * Fix import grouping in tests * Build locale dir env var name consistently * Updated from global requirements * Remove Babel version workaround code * Trivial refactors for gettextutils * Setup for translation * Update the public API of the library * Check the lazy flag at runtime * Handle . and - in translation domains * Split up monolithic test file * Updated from global requirements * Fix up usage instructions * fix docstring for fakes module * Update default tox settings * update .gitreview * update tests for python 3 * sync cross-test script from incubator * pep8 fixes from import * update .gitignore with new lib name * Make unit tests pass * initial export with graduate.sh * Add API for creating translation functions * Use oslotest instead of common test module * Fix test\_gettextutils on Python 3 * Fix gettextutil.Message handling of deep copy failures * Change lazy translation to retain complete dict * Remove requirements.txt from .gitignore * Add etc/openstack.conf.sample to .gitignore * Add support for translating log levels separately * Fix E501 in individual openstack projects * Fix test method use * Make Message keep string interpolation args * Add support for locales missing from babel * Allow the Message class to have non-English default locales * Implementation of translation log handler * Use hacking import\_exceptions for gettextutils.\_ * Translation Message improvements * Fix violations of H302:import only modules * Fixed misspellings of common words * Trivial: Make vertical white space after license header consistent * Remove vim header * Use six.text\_type instead of unicode function in tests * Fix typos in oslo * Adjust import order according to PEP8 imports rule * Replace assertEquals with assertEqual * When translating if no locale is given use default locale * Type check for Message param to avoid AttributeError * gettextutils: port to Python 3 * Translate all substitution elements of a Message object * python3: Fix UserString import * Replace using tests.utils part2 * Enable multiple translation domains for gettextutils * Bump hacking to 0.7.0 * Allow mapping \_ to lazy gettext path * Fix Message format-string parsing * Add common methods required to allow translation of REST API responses * Add eclipse project files to .gitignore * Add more robust gettext interpolation handling * Add .testrepository to .gitignore * python3: Add basic python3 compatibility * Enable hacking H404 test * Add basic lazy gettext implementation * Ignore backup files in .gitignore * Support overriding oslo localedir too * Add a gettextutils.install() helper function * gettextutils: fix translation domain * Fix Copyright Headers - Rename LLC to Foundation * Add join\_consumer\_pool() to RPC connections * Replace direct use of testtools BaseTestCase * Use testtools as test base class * Fixes import order errors * Add common base weigher/weigher handler for filter scheduler * updating sphinx documentation * Correcting openstack-common mv to oslo-incubator * Update .gitreview for oslo * .gitignore updates for generated files * Add gettext support * Updated tox config for multi-python testing * Added .gitreview file * ignore cover's html directory * Rajaram/Vinkesh|increased tests for Request and Response serializers * Rajaram/Vinkesh|Added nova's serializaiton classes into common * Initial skeleton project oslo.i18n-4.0.1/tox.ini0000664000175000017500000000337313643050246014614 0ustar zuulzuul00000000000000[tox] minversion = 3.1.1 envlist = py37,pep8 ignore_basepython_conflict = true [testenv] basepython = python3 deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = stestr run --slowest {posargs} [testenv:pep8] deps = -r{toxinidir}/test-requirements.txt commands = flake8 # Run security linter bandit -r oslo_i18n -x tests -n5 [testenv:venv] commands = {posargs} [testenv:docs] whitelist_externals = rm deps = {[testenv]deps} -r{toxinidir}/doc/requirements.txt commands = rm -rf doc/build sphinx-build -W --keep-going -b html doc/source doc/build/html [testenv:cover] setenv = PYTHON=coverage run --source oslo_i18n --parallel-mode commands = stestr run {posargs} coverage combine coverage html -d cover coverage xml -o cover/coverage.xml [flake8] # E123, E125 skipped as they are invalid PEP-8. # E731 skipped as assign a lambda expression show-source = True ignore = E123,E125,E731,W504 # H106: Don’t put vim configuration in source files # H203: Use assertIs(Not)None to check for None enable-extensions=H106,H203 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,__init__.py [hacking] import_exceptions = oslo_i18n._i18n._ [testenv:releasenotes] whitelist_externals = rm deps = -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/doc/requirements.txt commands = rm -rf releasenotes/build sphinx-build -a -E -W -d releasenotes/build/doctrees --keep-going -b html releasenotes/source releasenotes/build/html [testenv:lower-constraints] deps = -c{toxinidir}/lower-constraints.txt -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt oslo.i18n-4.0.1/.mailmap0000664000175000017500000000013013643050246014706 0ustar zuulzuul00000000000000# Format is: # # oslo.i18n-4.0.1/CONTRIBUTING.rst0000664000175000017500000000103313643050246015731 0ustar zuulzuul00000000000000If you would like to contribute to the development of OpenStack, you must follow the steps in this page: http://docs.openstack.org/infra/manual/developers.html Once those steps have been completed, changes to OpenStack should be submitted for review via the Gerrit tool, following the workflow documented at: http://docs.openstack.org/infra/manual/developers.html#development-workflow Pull requests submitted through GitHub will be ignored. Bugs should be filed on Launchpad, not GitHub: https://bugs.launchpad.net/oslo.i18noslo.i18n-4.0.1/doc/0000775000175000017500000000000013643050342014035 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/doc/requirements.txt0000664000175000017500000000052313643050246017324 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. sphinx>=1.8.0,!=2.1.0 # BSD openstackdocstheme>=1.18.1 # Apache-2.0 reno>=2.5.0 # Apache-2.0 sphinxcontrib-apidoc>=0.2.0 # BSD oslo.i18n-4.0.1/doc/source/0000775000175000017500000000000013643050342015335 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/doc/source/reference/0000775000175000017500000000000013643050342017273 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/doc/source/reference/index.rst0000664000175000017500000000016513643050246021141 0ustar zuulzuul00000000000000.. _api: ========================= oslo.i18n API Reference ========================= .. toctree:: api/modules oslo.i18n-4.0.1/doc/source/conf.py0000775000175000017500000000366113643050246016650 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # -- General configuration ---------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinxcontrib.apidoc', 'openstackdocstheme' ] # openstackdocstheme options repository_name = 'openstack/oslo.i18n' bug_project = 'oslo.i18n' bug_tag = '' html_last_updated_fmt = '%Y-%m-%d %H:%M' # The master toctree document. master_doc = 'index' # General information about the project. project = u'oslo.i18n' copyright = u'2014, OpenStack Foundation' # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'openstackdocs' # -- sphinxcontrib.apidoc configuration -------------------------------------- apidoc_module_dir = '../../oslo_i18n' apidoc_output_dir = 'reference/api' apidoc_excluded_paths = [ 'tests', ] oslo.i18n-4.0.1/doc/source/index.rst0000664000175000017500000000072613643050246017206 0ustar zuulzuul00000000000000================================================== oslo.i18n -- Oslo Internationalization Utilities ================================================== The oslo.i18n library contain utilities for working with internationalization (i18n) features, especially translation for text strings in an application or library. .. toctree:: :maxdepth: 2 user/index reference/index contributor/index .. rubric:: Indices and tables * :ref:`modindex` * :ref:`search` oslo.i18n-4.0.1/doc/source/contributor/0000775000175000017500000000000013643050342017707 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/doc/source/contributor/policy.rst0000664000175000017500000000075313643050246021750 0ustar zuulzuul00000000000000================ Policy History ================ * `Discussion from Havana Summit `__ * `Discussion from Icehouse Summit `__ * `Discussion from Juno Summit `__ * `I18n team wiki page `__ * `LoggingStandards wiki page `__ oslo.i18n-4.0.1/doc/source/contributor/index.rst0000664000175000017500000000021413643050246021550 0ustar zuulzuul00000000000000=========================== Contributing to oslo.i18n =========================== .. toctree:: :maxdepth: 2 contributing policy oslo.i18n-4.0.1/doc/source/contributor/contributing.rst0000664000175000017500000000012413643050246023150 0ustar zuulzuul00000000000000============== Contributing ============== .. include:: ../../../CONTRIBUTING.rst oslo.i18n-4.0.1/doc/source/user/0000775000175000017500000000000013643050342016313 5ustar zuulzuul00000000000000oslo.i18n-4.0.1/doc/source/user/index.rst0000664000175000017500000000016713643050246020163 0ustar zuulzuul00000000000000================= Using oslo.i18n ================= .. toctree:: :maxdepth: 2 usage guidelines history oslo.i18n-4.0.1/doc/source/user/history.rst0000664000175000017500000000004013643050246020543 0ustar zuulzuul00000000000000.. include:: ../../../ChangeLog oslo.i18n-4.0.1/doc/source/user/guidelines.rst0000664000175000017500000001522513643050246021205 0ustar zuulzuul00000000000000================================= Guidelines for Use in OpenStack ================================= The OpenStack I18N team has a limited capacity to translate messages, so we want to make their work as effective as possible by identifying the most useful text for them to translate. All text messages *the user sees* via exceptions or API calls should be marked for translation. However, some exceptions are used internally to signal error conditions between modules and are not intended to be presented to the user. Those do not need to be translated. Neither do log messages, as explained below. .. seealso:: * :ref:`usage` * :ref:`api` Gettext Contextual Form and Plural Form ======================================= Sometimes under different contexts, the same word should be translated into different phrases using :py:attr:`TranslatorFactory.contextual_form `. And recommend the following code to use contextual form:: # The contextual translation function using the name "_C" _C = _translators.contextual_form ... msg = _C('context', 'string') In some languages, sometimes the translated strings are different with different item counts using :py:attr:`TranslatorFactory.plural_form ` And recommend the following code to use plural form:: # The plural translation function using the name "_P" _P = _translators.plural_form ... msg = _P('single', 'plural', count) The contextual form and plural form are used only when needed. By default, the translation should use the ``_()``. .. note:: These two functions were only available in oslo.i18n >= 2.1.0. Log Translation =============== .. note:: Starting with the Pike series, OpenStack no longer supports log translation. It is not necessary to add translation instructions to new code, and the instructions can be removed from old code. The following documentation is retained to help developers understand existing usage and how to remove it. Support was `dropped `_ primarily based on `feedback from operators `_ that they were not only not needed but also undesirable, because they fragmented the set of web pages providing helpful information about any particular log message, thereby reducing the chances of finding those web pages by doing a web search for the message. Refer to the email thread `understanding log domain change `_ on the openstack-dev mailing list for more details. OpenStack previously supported translating some log levels using separate message catalogs, and so has separate marker functions. These well-known names were used by the build system jobs that extracted the messages from the source code and passed them to the translation tool. ========== ========== Level Function ========== ========== INFO ``_LI()`` WARNING ``_LW()`` ERROR ``_LE()`` CRITICAL ``_LC()`` ========== ========== .. note:: Debug level log messages were never translated. Using a Marker Function ======================= The marker functions are used to mark the translatable strings in the code. The strings are extracted into catalogs using a tool that performs source inspection to look for these specific markers, so the function argument must just be a string. For example: **do not do this**:: # WRONG msg = _(variable_containing_msg) Instead, use this style:: # RIGHT msg = _('My message.') Choosing a Marker Function ========================== The purpose of the different marker functions is to separate the translatable messages into different catalogs, which the translation teams can prioritize translating. It is important to choose the right marker function, to ensure that strings the user sees will be translated and to help the translation team manage their work load. Everything marked with ``_()`` will be translated. Prioritizing the catalogs created from strings marked with the log marker functions is up to the individual translation teams and their users, but it is expected that they will work on critical and error messages before warning or info. ``_()`` is preferred for any user facing message, even if it is also going to a log file. This ensures that the translated version of the message will be available to the user. The log marker functions (``_LI()``, ``_LW()``, ``_LE()``, and ``_LC()``) should no longer be used, and existing usages should be removed. Anytime that the message is passed outside of the current context (for example as part of an exception) the ``_()`` marker function must be used instead. A common pattern used to be to define a single message object and use it more than once, for the log call and the exception. In that case, ``_()`` had to be used because the message was going to appear in an exception that may be presented to the user. However, now that log messages are no longer translated, it is unfortunately necessary to use two separate strings: a plain one for the log message, and a translatable one for the exception. For example, **do not do this**:: # WRONG msg = _('There was an error.') LOG.error(msg) raise LocalExceptionClass(msg) or this:: # EVEN MORE WRONG msg = _LE('There was an error.') LOG.error(msg) raise LocalExceptionClass(msg) Instead, use this style:: # RIGHT LOG.error('There was an error.') raise LocalExceptionClass(_('An error occurred.')) Adding Variables to Translated Messages ======================================= Translated messages should not be combined with other literal strings to create partially translated messages. For example, **do not do this**:: # WRONG raise ValueError(_('some message') + ': variable=%s' % variable) Instead, use this style:: # RIGHT raise ValueError(_('some message: variable=%s') % variable) Including the variable reference inside the translated message allows the translator to take into account grammar rules, differences in left-right vs. right-left rendering, and other factors to make the translated message more useful to the end user. Any message with more than one variable should use named interpolation instead of positional, to allow translators to move the variables around in the string to account for differences in grammar and writing direction. For example, **do not do this**:: # WRONG raise ValueError(_('some message: v1=%s v2=%s') % (v1, v2)) Instead, use this style:: # RIGHT raise ValueError(_('some message: v1=%(v1)s v2=%(v2)s') % {'v1': v1, 'v2': v2}) oslo.i18n-4.0.1/doc/source/user/usage.rst0000664000175000017500000001734713643050246020170 0ustar zuulzuul00000000000000.. _usage: ===================================================== How to Use oslo.i18n in Your Application or Library ===================================================== Installing ========== At the command line:: $ pip install oslo.i18n .. _integration-module: Creating an Integration Module ============================== To use oslo.i18n in a project (e.g. myapp), you will need to create a small integration module to hold an instance of :class:`~oslo_i18n.TranslatorFactory` and references to the marker functions the factory creates. .. note:: Libraries probably do not want to expose the new integration module as part of their public API, so rather than naming it ``myapp.i18n`` it should be called ``myapp._i18n`` to indicate that it is a private implementation detail, and not meant to be used outside of the library's own code. .. note:: Starting with the Pike series, OpenStack no longer supports log translation. It is not necessary to add translation instructions to new code, and the instructions can be removed from old code. Refer to the email thread `understanding log domain change `_ on the openstack-dev mailing list for more details. .. code-block:: python # myapp/_i18n.py import oslo_i18n DOMAIN = "myapp" _translators = oslo_i18n.TranslatorFactory(domain=DOMAIN) # The primary translation function using the well-known name "_" _ = _translators.primary # The contextual translation function using the name "_C" # requires oslo.i18n >=2.1.0 _C = _translators.contextual_form # The plural translation function using the name "_P" # requires oslo.i18n >=2.1.0 _P = _translators.plural_form def get_available_languages(): return oslo_i18n.get_available_languages(DOMAIN) .. TODO: Provide examples for _C and _P Then, in the rest of your code, use the appropriate marker function for each message: .. code-block:: python from myapp._i18n import _ # ... variable = "openstack" some_object.name_msg = _('my name is: %s') % variable # ... try: # ... except AnException1: # Log only, log messages are no longer translated LOG.exception('exception message') except AnException2: # Raise only raise RuntimeError(_('exception message')) else: # Log and Raise msg = _('Unexpected error message') LOG.exception(msg) raise RuntimeError(msg) .. note:: The import of multiple modules from _i18n on a single line is a valid exception to `OpenStack Style Guidelines `_ for import statements. It is important to use the marker functions (e.g. _), rather than the longer form of the name, because the tool that scans the source code for translatable strings looks for the marker function names. .. warning:: The old method of installing a version of ``_()`` in the builtins namespace is deprecated. Modifying the global namespace affects libraries as well as the application, so it may interfere with proper message catalog lookups. Calls to :func:`gettextutils.install` should be replaced with the application or library integration module described here. Handling hacking Objections to Imports ====================================== The `OpenStack Style Guidelines `_ prefer importing modules and accessing names from those modules after import, rather than importing the names directly. For example: :: # WRONG from foo import bar bar() # RIGHT import foo foo.bar() The linting tool hacking_ will typically complain about importing names from within modules. It is acceptable to bypass this for the translation marker functions, because they must have specific names and their use pattern is dictated by the message catalog extraction tools rather than our style guidelines. To bypass the hacking check for imports from this integration module, add an import exception to ``tox.ini``. For example:: # tox.ini [hacking] import_exceptions = myapp._i18n .. _hacking: https://pypi.org/project/hacking .. _lazy-translation: Lazy Translation ================ Lazy translation delays converting a message string to the translated form as long as possible, including possibly never if the message is not logged or delivered to the user in some other way. It also supports logging translated messages in multiple languages, by configuring separate log handlers. Lazy translation is implemented by returning a special object from the translation function, instead of a unicode string. That special message object supports some, but not all, string manipulation APIs. For example, concatenation with addition is not supported, but interpolation of variables is supported. Depending on how translated strings are used in an application, these restrictions may mean that lazy translation cannot be used, and so it is not enabled by default. To enable lazy translation, call :func:`enable_lazy`. :: import oslo_i18n oslo_i18n.enable_lazy() Translating Messages ==================== Use :func:`~oslo_i18n.translate` to translate strings to a specific locale. :func:`translate` handles delayed translation and strings that have already been translated immediately. It should be used at the point where the locale to be used is known, which is often just prior to the message being returned or a log message being emitted. :: import oslo_i18n trans_msg = oslo_i18n.translate(msg, my_locale) If a locale is not specified the default locale is used. Available Languages =================== Only the languages that have translations provided are available for translation. To determine which languages are available the :func:`~oslo_i18n.get_available_languages` is provided. The integration module provides a domain defined specific function. .. code-block:: python import myapp._i18n languages = myapp._i18n.get_available_languages() .. seealso:: * :doc:`guidelines` Displaying translated messages ============================== Several preparations are required to display translated messages in your running application. Preferred language You need to specify your preferred language through an environment variable. The preferred language can be specified by ``LANGUAGE``, ``LC_ALL``, ``LC_MESSAGES``, or ``LANGUAGE`` (A former one has a priority). ``oslo_i18n.translate()`` can be used to translate a string to override the preferred language. .. note:: You need to use ``enable_lazy()`` to override the preferred language by using ``oslo_i18n.translate()``. Locale directory Python ``gettext`` looks for binary ``mo`` files for the given domain using the path ``//LC_MESSAGES/.mo``. The default locale directory varies on distributions, and it is ``/usr/share/locale`` in most cases. If you store message catalogs in a different location, you need to specify the location via an environment variable named ``_LOCALEDIR`` where ```` is an upper-case domain name with replacing ``_`` and ``.`` with ``-``. For example, ``NEUTRON_LOCALEDIR`` for a domain ``neutron`` and ``OSLO_I18N_LOCALEDIR`` for a domain ``oslo_i18n``. .. note:: When you specify locale directories via ``_LOCALEDIR`` environment variables, you need to specify an environment variable per domain. More concretely, if your application using a domain ``myapp` uses oslo.policy, you need to specify both ``MYAPP_LOCALEDIR`` and ``OSLO_POLICY_LOCALEDIR`` to ensure that translation messages from both your application and oslo.policy are displayed.