Retire repo
This repo was created by accident, use deb-python-oslo.i18n instead. Needed-By: I1ac1a06931c8b6dd7c2e73620a0302c29e605f03 Change-Id: I81894aea69b9d09b0977039623c26781093a397a
This commit is contained in:
parent
16f40a6392
commit
4759020bbf
@ -1,8 +0,0 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = oslo_i18n
|
||||
omit = oslo_i18n/tests/*
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
||||
precision = 2
|
24
.gitignore
vendored
24
.gitignore
vendored
@ -1,24 +0,0 @@
|
||||
*~
|
||||
*.swp
|
||||
*.pyc
|
||||
*.log
|
||||
.coverage
|
||||
.venv
|
||||
.tox
|
||||
cover/
|
||||
oslo.i18n.egg-info/
|
||||
.openstack-common-venv/
|
||||
skeleton.egg-info/
|
||||
build/
|
||||
dist/
|
||||
doc/source/api
|
||||
AUTHORS
|
||||
.update-venv/
|
||||
ChangeLog
|
||||
openstack/versioninfo
|
||||
*.egg
|
||||
openstack/common/db/*.sqlite
|
||||
.testrepository/
|
||||
.project
|
||||
.pydevproject
|
||||
etc/openstack.conf.sample
|
@ -1,4 +0,0 @@
|
||||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/oslo.i18n.git
|
3
.mailmap
3
.mailmap
@ -1,3 +0,0 @@
|
||||
# Format is:
|
||||
# <preferred e-mail> <other e-mail 1>
|
||||
# <preferred e-mail> <other e-mail 2>
|
@ -1,7 +0,0 @@
|
||||
[DEFAULT]
|
||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
||||
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
||||
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
|
||||
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
|
||||
test_id_option=--load-list $IDFILE
|
||||
test_list_option=--list
|
@ -1,16 +0,0 @@
|
||||
If 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.i18n
|
@ -1,4 +0,0 @@
|
||||
Style Commandments
|
||||
===============================================
|
||||
|
||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
175
LICENSE
175
LICENSE
@ -1,175 +0,0 @@
|
||||
|
||||
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.
|
20
README.rst
20
README.rst
@ -1,20 +0,0 @@
|
||||
==================================================
|
||||
oslo.i18n -- Oslo Internationalization Utilities
|
||||
==================================================
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/oslo.i18n.svg
|
||||
:target: https://pypi.python.org/pypi/oslo.i18n/
|
||||
:alt: Latest Version
|
||||
|
||||
.. image:: https://img.shields.io/pypi/dm/oslo.i18n.svg
|
||||
:target: https://pypi.python.org/pypi/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: http://docs.openstack.org/developer/oslo.i18n
|
||||
* Source: http://git.openstack.org/cgit/openstack/oslo.i18n
|
||||
* Bugs: http://bugs.launchpad.net/oslo.i18n
|
13
README.txt
Normal file
13
README.txt
Normal file
@ -0,0 +1,13 @@
|
||||
This project is no longer maintained.
|
||||
|
||||
The contents of this repository are still available in the Git
|
||||
source code management system. To see the contents of this
|
||||
repository before it reached its end of life, please check out the
|
||||
previous commit with "git checkout HEAD^1".
|
||||
|
||||
Use instead the project deb-python-oslo.i18n at
|
||||
http://git.openstack.org/cgit/openstack/deb-python-oslo.i18n .
|
||||
|
||||
For any further questions, please email
|
||||
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||
Freenode.
|
@ -1,39 +0,0 @@
|
||||
=====
|
||||
API
|
||||
=====
|
||||
|
||||
oslo_i18n
|
||||
=========
|
||||
|
||||
.. automodule:: oslo_i18n
|
||||
|
||||
.. autoclass:: oslo_i18n.TranslatorFactory
|
||||
:members:
|
||||
|
||||
.. seealso::
|
||||
|
||||
An example of using a :class:`TranslatorFactory` is provided in
|
||||
:ref:`integration-module`.
|
||||
|
||||
.. autofunction:: oslo_i18n.enable_lazy
|
||||
|
||||
.. seealso::
|
||||
|
||||
:ref:`lazy-translation`
|
||||
|
||||
.. autofunction:: oslo_i18n.translate
|
||||
|
||||
.. autofunction:: oslo_i18n.get_available_languages
|
||||
|
||||
oslo_i18n.log
|
||||
=============
|
||||
|
||||
.. automodule:: oslo_i18n.log
|
||||
:members:
|
||||
|
||||
oslo_i18n.fixture
|
||||
=================
|
||||
|
||||
.. automodule:: oslo_i18n.fixture
|
||||
:members:
|
||||
:special-members:
|
@ -1,77 +0,0 @@
|
||||
# -*- 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.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath('../..'))
|
||||
# -- 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',
|
||||
#'sphinx.ext.intersphinx',
|
||||
'oslosphinx'
|
||||
]
|
||||
|
||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
||||
# text edit cycles.
|
||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# 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_path = ["."]
|
||||
# html_theme = '_theme'
|
||||
# html_static_path = ['static']
|
||||
|
||||
html_use_modindex = True
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = '%sdoc' % project
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
('index',
|
||||
'%s.tex' % project,
|
||||
u'%s Documentation' % project,
|
||||
u'OpenStack Foundation', 'manual'),
|
||||
]
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
#intersphinx_mapping = {'http://docs.python.org/': None}
|
@ -1,5 +0,0 @@
|
||||
==============
|
||||
Contributing
|
||||
==============
|
||||
|
||||
.. include:: ../../CONTRIBUTING.rst
|
@ -1,237 +0,0 @@
|
||||
=================================
|
||||
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.
|
||||
|
||||
.. seealso::
|
||||
|
||||
* :doc:`usage`
|
||||
* :doc:`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 <oslo_i18n.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 <oslo_i18n.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
|
||||
===============
|
||||
|
||||
OpenStack supports translating some log levels using separate message
|
||||
catalogs, and so has separate marker functions. These well-known names
|
||||
are used by the build system jobs that extract the messages from the
|
||||
source code and pass it to the translation tool.
|
||||
|
||||
========== ==========
|
||||
Level Function
|
||||
========== ==========
|
||||
INFO ``_LI()``
|
||||
WARNING ``_LW()``
|
||||
ERROR ``_LE()``
|
||||
CRITICAL ``_LC()``
|
||||
========== ==========
|
||||
|
||||
.. note::
|
||||
* Debug level log messages are not translated.
|
||||
* LOG.exception creates an ERROR level log, so when a marker function is
|
||||
used (see below) ``_LE()`` should be used.
|
||||
|
||||
|
||||
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
|
||||
looks for these specific markers, so the function argument must just
|
||||
be a string.
|
||||
|
||||
For example: **do not do this**::
|
||||
|
||||
# WRONG
|
||||
msg = _(variable_containing_msg)
|
||||
w_msg = _LW(variable_warning_msg)
|
||||
|
||||
Instead, use this style::
|
||||
|
||||
# RIGHT
|
||||
msg = _('My message.')
|
||||
w_msg = _LW('My warning 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()``)
|
||||
must only be used when the message is only sent directly to the log.
|
||||
Anytime that the message will be passed outside of the current context
|
||||
(for example as part of an exception) the ``_()`` marker function
|
||||
must be used.
|
||||
|
||||
A common pattern is to define a single message object and use it more
|
||||
than once, for the log call and the exception. In that case, ``_()``
|
||||
must be used because the message is going to appear in an exception that
|
||||
may be presented to the user.
|
||||
|
||||
For example, **do not do this**::
|
||||
|
||||
# WRONG
|
||||
msg = _LE('There was an error.')
|
||||
LOG.exception(msg)
|
||||
raise LocalExceptionClass(msg)
|
||||
|
||||
Instead, use this style::
|
||||
|
||||
# RIGHT
|
||||
msg = _('There was an error.')
|
||||
LOG.exception(msg)
|
||||
raise LocalExceptionClass(msg)
|
||||
|
||||
Except in the case above, ``_()`` should not be used for translating
|
||||
log messages. This avoids having the same string in two message
|
||||
catalogs, possibly translated differently by two different
|
||||
translators. The log message will translate properly because when
|
||||
the message is not found in the log specific catalog the ``_()``
|
||||
catalog will be used.
|
||||
|
||||
If a common message is not being used, they should each be treated
|
||||
separately with respect to choosing a marker function.
|
||||
|
||||
For example, **do not do this**::
|
||||
|
||||
# WRONG
|
||||
LOG.exception(_('There was an error.'))
|
||||
raise LocalExceptionClass(_('An error occured.'))
|
||||
|
||||
Instead, use this style::
|
||||
|
||||
# RIGHT
|
||||
LOG.exception(_LE('There was an error.'))
|
||||
raise LocalExceptionClass(_('An error occured.'))
|
||||
|
||||
|
||||
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})
|
||||
|
||||
|
||||
Adding Variables to Log Messages
|
||||
================================
|
||||
|
||||
String interpolation should be delayed to be handled by the logging
|
||||
code, rather than being done at the point of the logging call. For
|
||||
example, **do not do this**::
|
||||
|
||||
# WRONG
|
||||
LOG.info(_LI('some message: variable=%s') % variable)
|
||||
|
||||
Instead, use this style::
|
||||
|
||||
# RIGHT
|
||||
LOG.info(_LI('some message: variable=%s'), variable)
|
||||
|
||||
This allows the logging package to skip creating the formatted log
|
||||
message if the message is not going to be emitted because of the
|
||||
current log level.
|
||||
|
||||
Avoid Forcing the Translation of Translatable Variables
|
||||
=======================================================
|
||||
|
||||
Translation can also be delayed for variables that potentially contain
|
||||
translatable objects such as exceptions.
|
||||
|
||||
Whenever possible translation should not be forced by use of :func:`str`,
|
||||
:func:`unicode`, or :func:`six.text_type` on a message being used with
|
||||
a format string.
|
||||
|
||||
For example, **do not do this**::
|
||||
|
||||
# WRONG
|
||||
LOG.info(_LI('some message: exception=%s'), six.text_type(exc))
|
||||
|
||||
Instead, use this style::
|
||||
|
||||
# RIGHT
|
||||
LOG.info(_LI('some message: exception=%s'), exc)
|
||||
|
||||
This allows the translation of the translatable replacement text to be
|
||||
delayed until the message is translated.
|
@ -1 +0,0 @@
|
||||
.. include:: ../../ChangeLog
|
@ -1,33 +0,0 @@
|
||||
==================================================
|
||||
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.
|
||||
|
||||
Contents
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
usage
|
||||
guidelines
|
||||
api
|
||||
policy
|
||||
contributing
|
||||
|
||||
Release Notes
|
||||
=============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
history
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
@ -1,9 +0,0 @@
|
||||
================
|
||||
Policy History
|
||||
================
|
||||
|
||||
* `Discussion from Havana Summit <https://etherpad.openstack.org/p/havana-oslo-i18n-strategy>`__
|
||||
* `Discussion from Icehouse Summit <https://etherpad.openstack.org/p/icehouse-oslo-i18n-policies>`__
|
||||
* `Discussion from Juno Summit <https://etherpad.openstack.org/p/juno-cross-project-i18n>`__
|
||||
* `I18n team wiki page <https://wiki.openstack.org/wiki/I18n>`__
|
||||
* `LoggingStandards wiki page <https://wiki.openstack.org/wiki/LoggingStandards>`__
|
@ -1,218 +0,0 @@
|
||||
=====================================================
|
||||
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.
|
||||
|
||||
.. 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
|
||||
|
||||
# Translators for log levels.
|
||||
#
|
||||
# The abbreviated names are meant to reflect the usual use of a short
|
||||
# name like '_'. The "L" is for "log" and the other letter comes from
|
||||
# the level.
|
||||
_LI = _translators.log_info
|
||||
_LW = _translators.log_warning
|
||||
_LE = _translators.log_error
|
||||
_LC = _translators.log_critical
|
||||
|
||||
|
||||
def get_available_languages():
|
||||
return oslo_i18n.get_available_languages(DOMAIN)
|
||||
|
||||
Then, in the rest of your code, use the appropriate marker function
|
||||
for each message:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from myapp._i18n import _, _LW, _LE
|
||||
|
||||
# ...
|
||||
|
||||
variable = "openstack"
|
||||
LOG.warning(_LW('warning message: %s'), variable)
|
||||
|
||||
# ...
|
||||
|
||||
try:
|
||||
|
||||
# ...
|
||||
|
||||
except AnException1:
|
||||
|
||||
# Log only
|
||||
LOG.exception(_LE('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 <http://docs.openstack.org/developer/hacking/#imports>`_
|
||||
for import statements.
|
||||
|
||||
|
||||
It is important to use the marker functions (e.g. _LI), 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 <http://docs.openstack.org/developer/hacking/#imports>`_
|
||||
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.python.org/pypi/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`
|
@ -1,16 +0,0 @@
|
||||
# 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 *
|
@ -1,205 +0,0 @@
|
||||
# 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')
|
@ -1,90 +0,0 @@
|
||||
# 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
|
||||
import six
|
||||
|
||||
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 six.iteritems(aliases)
|
||||
if (locale in language_list and
|
||||
alias not in language_list))
|
||||
|
||||
_AVAILABLE_LANGUAGES[domain] = language_list
|
||||
return copy.copy(language_list)
|
@ -1,25 +0,0 @@
|
||||
# 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
|
@ -1,38 +0,0 @@
|
||||
# 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
|
@ -1,25 +0,0 @@
|
||||
# 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'
|
@ -1,233 +0,0 @@
|
||||
# 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):
|
||||
"""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)
|
@ -1,73 +0,0 @@
|
||||
# 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.translate(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 six.iteritems(args))
|
||||
return translated_dict
|
||||
return translate(args, desired_locale)
|
@ -1,165 +0,0 @@
|
||||
# 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))
|
@ -1,26 +0,0 @@
|
||||
# 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 <jaegerandi@gmail.com>, 2014
|
||||
# Robert Simai, 2015
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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 <jaegerandi@gmail.com>\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."
|
@ -1,25 +0,0 @@
|
||||
# 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 <andi@gowling.com>, 2014
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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-02 09:09+0000\n"
|
||||
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
|
||||
"Language: en-GB\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Generated-By: Babel 2.0\n"
|
||||
"X-Generator: Zanata 3.7.3\n"
|
||||
"Language-Team: English (United Kingdom)\n"
|
||||
|
||||
msgid "Message objects do not support addition."
|
||||
msgstr "Message objects do not support addition."
|
@ -1,25 +0,0 @@
|
||||
# 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 <achisco94@gmail.com>, 2015
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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 <achisco94@gmail.com>\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."
|
@ -1,26 +0,0 @@
|
||||
# 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 <jonathan+transifex@dupart.org>, 2014
|
||||
# Maxime COQUEREL <max.coquerel@gmail.com>, 2014
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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 <jonathan+transifex@dupart.org>\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."
|
@ -1,25 +0,0 @@
|
||||
# Translations template for oslo.i18n.
|
||||
# Copyright (C) 2015 ORGANIZATION
|
||||
# This file is distributed under the same license as the oslo.i18n project.
|
||||
#
|
||||
# Translators:
|
||||
# PierAlberto <pieralbertopierini@gmail.com>, 2014
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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 <pieralbertopierini@gmail.com>\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."
|
@ -1,18 +0,0 @@
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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 <kato.tomoyuki@jp.fujitsu.com>\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 "メッセージオブジェクトは追加機能をサポートしていません。"
|
@ -1,25 +0,0 @@
|
||||
# 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 <potopro@gmail.com>, 2014
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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-24 04:32+0000\n"
|
||||
"Last-Translator: Sungjin Kang <potopro@gmail.com>\n"
|
||||
"Language: ko-KR\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"Generated-By: Babel 2.0\n"
|
||||
"X-Generator: Zanata 3.7.3\n"
|
||||
"Language-Team: Korean (South Korea)\n"
|
||||
|
||||
msgid "Message objects do not support addition."
|
||||
msgstr "메시지 객체는 추가 지원을하지 않습니다."
|
@ -1,26 +0,0 @@
|
||||
# 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ś <deejay1@srem.org>, 2014
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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-21 11:28+0000\n"
|
||||
"Last-Translator: Łukasz Jernaś <deejay1@srem.org>\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 3.7.3\n"
|
||||
"Language-Team: Polish (Poland)\n"
|
||||
|
||||
msgid "Message objects do not support addition."
|
||||
msgstr "Obiekty Message nie wspierają dodawania."
|
@ -1,25 +0,0 @@
|
||||
# Translations template for oslo.i18n.
|
||||
# Copyright (C) 2015 ORGANIZATION
|
||||
# This file is distributed under the same license as the oslo.i18n project.
|
||||
#
|
||||
# Translators:
|
||||
# MMSRS <h_manuela_rodsilva@gmail.com>, 2015
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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 <h_manuela_rodsilva@gmail.com>\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."
|
@ -1,25 +0,0 @@
|
||||
# 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 <liuxx@cn.ibm.com>, 2014
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 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-11-20 12:40+0000\n"
|
||||
"Last-Translator: Xiao Xi LIU <liuxx@cn.ibm.com>\n"
|
||||
"Language: zh-CN\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"Generated-By: Babel 2.0\n"
|
||||
"X-Generator: Zanata 3.7.3\n"
|
||||
"Language-Team: Chinese (China)\n"
|
||||
|
||||
msgid "Message objects do not support addition."
|
||||
msgstr "消息对象不支持添加操作。"
|
@ -1,97 +0,0 @@
|
||||
# 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)
|
@ -1,59 +0,0 @@
|
||||
# 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
|
@ -1,147 +0,0 @@
|
||||
# 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 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_py2(self):
|
||||
_lazy.enable_lazy(False)
|
||||
with mock.patch.object(six, 'PY3', False):
|
||||
with mock.patch('gettext.translation') as translation:
|
||||
trans = mock.Mock()
|
||||
translation.return_value = trans
|
||||
trans.gettext.side_effect = AssertionError(
|
||||
'should have called ugettext')
|
||||
tf = _factory.TranslatorFactory('domain')
|
||||
tf.primary('some text')
|
||||
trans.ugettext.assert_called_with('some text')
|
||||
|
||||
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_py2(self):
|
||||
_lazy.enable_lazy(False)
|
||||
with mock.patch.object(six, 'PY3', False):
|
||||
with mock.patch('gettext.translation') as translation:
|
||||
trans = mock.Mock()
|
||||
translation.return_value = trans
|
||||
trans.gettext.side_effect = AssertionError(
|
||||
'should have called ugettext')
|
||||
trans.ugettext.return_value = "some text"
|
||||
tf = _factory.TranslatorFactory('domain')
|
||||
tf.contextual_form('context', 'some text')
|
||||
trans.ugettext.assert_called_with(
|
||||
"%s%s%s" % ('context', CONTEXT_SEPARATOR, 'some text'))
|
||||
|
||||
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_py2(self):
|
||||
_lazy.enable_lazy(False)
|
||||
with mock.patch.object(six, 'PY3', False):
|
||||
with mock.patch('gettext.translation') as translation:
|
||||
trans = mock.Mock()
|
||||
translation.return_value = trans
|
||||
trans.ngettext.side_effect = AssertionError(
|
||||
'should have called ungettext')
|
||||
tf = _factory.TranslatorFactory('domain')
|
||||
tf.plural_form('single', 'plural', 1)
|
||||
trans.ungettext.assert_called_with(
|
||||
'single', 'plural', 1)
|
||||
|
||||
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)
|
@ -1,118 +0,0 @@
|
||||
# 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(msg.msgid, 'this is a lazy message')
|
||||
|
||||
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(msg, u'this is a lazy message')
|
||||
|
||||
|
||||
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))
|
@ -1,131 +0,0 @@
|
||||
# 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 babel import localedata
|
||||
import mock
|
||||
from oslotest import base as test_base
|
||||
from oslotest import moxstubout
|
||||
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()
|
||||
moxfixture = self.useFixture(moxstubout.MoxStubout())
|
||||
self.stubs = moxfixture.stubs
|
||||
self.mox = moxfixture.mox
|
||||
# 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']
|
||||
|
||||
self.stubs.Set(localedata,
|
||||
'list' if hasattr(localedata, 'list')
|
||||
else 'locale_identifiers',
|
||||
_mock_locale_identifiers)
|
||||
|
||||
# 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
|
||||
self.stubs.Set(gettext, 'find', _mock_gettext_find)
|
||||
|
||||
# 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)
|
@ -1,106 +0,0 @@
|
||||
# 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
|
||||
|
||||
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())
|
@ -1,40 +0,0 @@
|
||||
# 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)
|
@ -1,32 +0,0 @@
|
||||
# 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)
|
@ -1,42 +0,0 @@
|
||||
# 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 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)
|
@ -1,687 +0,0 @@
|
||||
# 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
|
||||
import warnings
|
||||
|
||||
import mock
|
||||
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.translate())
|
||||
|
||||
def test_translate_returns_unicode(self):
|
||||
message = _message.Message('some %s') % 'message'
|
||||
self.assertIsInstance(message.translate(), 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(result, expected)
|
||||
self.assertEqual(result.translate(), expected)
|
||||
|
||||
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.translate(), first.translate())
|
||||
|
||||
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(result, expected)
|
||||
self.assertEqual(result.translate(), expected)
|
||||
|
||||
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.translate())
|
||||
|
||||
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.translate()
|
||||
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(result.translate(), message)
|
||||
|
||||
# simulate writing out as string
|
||||
result_str = '%s' % result.translate()
|
||||
self.assertEqual(result_str, message)
|
||||
self.assertEqual(result, message)
|
||||
|
||||
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(result.translate(), 'Found object: 1')
|
||||
|
||||
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(result.translate(), "Found list: [1, 2, 3]")
|
||||
|
||||
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(result.translate(), "Value: 5")
|
||||
|
||||
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(result.translate(), "Values: 1 2")
|
||||
|
||||
# Apply again to make sure other path works as well
|
||||
params = {'val1': 3, 'val2': utils.NoDeepCopyObject(4)}
|
||||
result = _message.Message(msgid) % params
|
||||
self.assertEqual(result.translate(), "Values: 3 4")
|
||||
|
||||
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.translate(),
|
||||
msgid % {'test1': 'foo', 'test2': 'bar'})
|
||||
self.assertEqual(m2.translate(),
|
||||
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.translate())
|
||||
|
||||
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(result, expected)
|
||||
self.assertEqual(result.translate(), expected)
|
||||
|
||||
# Make sure unused params still there
|
||||
self.assertEqual(result.params.keys(), 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_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
|
||||
|
||||
self.assertEqual(es_translation, message.translate('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.translate('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.translate('es'))
|
||||
self.assertEqual(zh_translation, message.translate('zh'))
|
||||
self.assertEqual(en_message, message.translate(None))
|
||||
self.assertEqual(en_message, message.translate('en'))
|
||||
self.assertEqual(en_message, message.translate('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.translate('es'))
|
||||
self.assertEqual(default_translation, msg.translate('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.translate('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.translate('es'))
|
||||
self.assertEqual(default_translation, msg.translate('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.translate('es'))
|
||||
self.assertEqual(default_translation, unicoded_obj.translate('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.translate('es'))
|
||||
self.assertEqual(default_translation, msg.translate('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.translate('es'))
|
||||
self.assertEqual(default_translation, msg.translate('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.translate('es'))
|
||||
self.assertEqual(default_translation, msg.translate('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.translate())
|
||||
self.assertEqual(es_translation, msg.translate('es'))
|
||||
|
||||
# Translation into other locales still works
|
||||
self.assertEqual(zh_translation, msg.translate('zh'))
|
||||
self.assertEqual(fr_translation, msg.translate('fr'))
|
||||
|
||||
|
||||
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,
|
||||
)
|
@ -1,44 +0,0 @@
|
||||
# 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
|
||||
|
||||
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')
|
@ -1,44 +0,0 @@
|
||||
# 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 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'))
|
@ -1,42 +0,0 @@
|
||||
# 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 unicode(self.value)
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
raise TypeError('Deep Copy not supported')
|
@ -1,7 +0,0 @@
|
||||
# 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>=1.6 # Apache-2.0
|
||||
Babel>=2.3.4 # BSD
|
||||
six>=1.9.0 # MIT
|
51
setup.cfg
51
setup.cfg
@ -1,51 +0,0 @@
|
||||
[metadata]
|
||||
name = oslo.i18n
|
||||
summary = Oslo i18n library
|
||||
description-file =
|
||||
README.rst
|
||||
author = OpenStack
|
||||
author-email = openstack-dev@lists.openstack.org
|
||||
home-page = http://wiki.openstack.org/wiki/Oslo#oslo.i18n
|
||||
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 :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.4
|
||||
|
||||
[files]
|
||||
packages =
|
||||
oslo_i18n
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/source
|
||||
build-dir = doc/build
|
||||
all_files = 1
|
||||
|
||||
[upload_sphinx]
|
||||
upload-dir = doc/build/html
|
||||
|
||||
[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
|
||||
|
||||
[pbr]
|
||||
warnerrors = True
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
29
setup.py
29
setup.py
@ -1,29 +0,0 @@
|
||||
# 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.
|
||||
|
||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||
import setuptools
|
||||
|
||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||
# setuptools if some other modules registered functions in `atexit`.
|
||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||
try:
|
||||
import multiprocessing # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr>=1.8'],
|
||||
pbr=True)
|
@ -1,15 +0,0 @@
|
||||
# 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<0.11,>=0.10.0
|
||||
|
||||
|
||||
sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
|
||||
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
||||
|
||||
mock>=2.0 # BSD
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
coverage>=3.6 # Apache-2.0
|
||||
|
||||
# for pre-release tests
|
||||
oslo.config>=3.12.0 # Apache-2.0
|
38
tox.ini
38
tox.ini
@ -1,38 +0,0 @@
|
||||
[tox]
|
||||
minversion = 1.6
|
||||
envlist = py34,py27,pep8
|
||||
|
||||
[testenv]
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
commands = python setup.py testr --slowest --testr-args='{posargs}'
|
||||
|
||||
[testenv:pep8]
|
||||
commands = flake8
|
||||
|
||||
[testenv:venv]
|
||||
commands = {posargs}
|
||||
|
||||
[testenv:docs]
|
||||
commands = python setup.py build_sphinx
|
||||
|
||||
[testenv:cover]
|
||||
commands = python setup.py test --coverage --coverage-package-name=oslo_i18n --testr-args='{posargs}'
|
||||
|
||||
[flake8]
|
||||
# E123, E125 skipped as they are invalid PEP-8.
|
||||
|
||||
show-source = True
|
||||
ignore = E123,E125
|
||||
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build,__init__.py
|
||||
|
||||
[hacking]
|
||||
import_exceptions =
|
||||
oslo_i18n._i18n._
|
||||
|
||||
[testenv:pip-missing-reqs]
|
||||
# do not install test-requirements as that will pollute the virtualenv for
|
||||
# determining missing packages
|
||||
# this also means that pip-missing-reqs must be installed separately, outside
|
||||
# of the requirements.txt files
|
||||
deps = pip_missing_reqs
|
||||
commands = pip-missing-reqs -d --ignore-module=oslo_i18n* --ignore-file=oslo_i18n/tests/* --ignore-file=tests/ oslo_i18n
|
Loading…
Reference in New Issue
Block a user