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:
Andreas Jaeger 2017-04-17 19:36:51 +02:00
parent 16f40a6392
commit 4759020bbf
57 changed files with 13 additions and 3726 deletions

View File

@ -1,8 +0,0 @@
[run]
branch = True
source = oslo_i18n
omit = oslo_i18n/tests/*
[report]
ignore_errors = True
precision = 2

24
.gitignore vendored
View File

@ -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

View File

@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/oslo.i18n.git

View File

@ -1,3 +0,0 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>

View File

@ -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

View File

@ -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

View File

@ -1,4 +0,0 @@
Style Commandments
===============================================
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/

175
LICENSE
View File

@ -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.

View File

@ -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
View 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.

View File

@ -1 +0,0 @@
[python: **.py]

View File

@ -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:

View File

@ -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}

View File

@ -1,5 +0,0 @@
==============
Contributing
==============
.. include:: ../../CONTRIBUTING.rst

View File

@ -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.

View File

@ -1 +0,0 @@
.. include:: ../../ChangeLog

View File

@ -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`

View File

@ -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>`__

View File

@ -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`

View File

@ -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 *

View File

@ -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')

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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)

View File

@ -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)

View File

@ -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))

View File

@ -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."

View File

@ -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."

View File

@ -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."

View File

@ -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."

View File

@ -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."

View File

@ -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 "メッセージオブジェクトは追加機能をサポートしていません。"

View File

@ -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 "메시지 객체는 추가 지원을하지 않습니다."

View File

@ -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."

View File

@ -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."

View File

@ -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 "消息对象不支持添加操作。"

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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))

View File

@ -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)

View File

@ -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())

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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,
)

View File

@ -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')

View File

@ -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'))

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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
View File

@ -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