Merge "Pseudo translation tool"

This commit is contained in:
Jenkins 2015-01-21 18:20:20 +00:00 committed by Gerrit Code Review
commit a38b728136
3 changed files with 140 additions and 4 deletions

@ -88,6 +88,7 @@ Once you've made your changes, there are a few things to do:
* Make sure the unit tests pass: ``./run_tests.sh``
* Make sure PEP8 is clean: ``./run_tests.sh --pep8``
* Make sure your code is ready for translation: ``./run_tests.sh --pseudo de`` See the Translatability section below for details.
* Make sure your code is up-to-date with the latest master: ``git pull --rebase``
* Finally, run ``git review`` to upload your changes to Gerrit for review.
@ -124,6 +125,42 @@ The community's guidelines for etiquette are fairly simple:
a piece of code, it's polite (though not required) to thank them in your
commit message.
Translatability
===============
Horizon gets translated into multiple languages. The pseudo translation tool
can be used to verify that code is ready to be translated. The pseudo tool
replaces a language's translation with a complete, fake translation. Then
you can verify that your code properly displays fake translations to validate
that your code is ready for translation.
Running the pseudo translation tool
-----------------------------------
#. Make sure your English po file is up to date: ``./run_tests.sh --makemessages``
#. Run the pseudo tool to create pseudo translations. For example, to replace the German translation with a pseudo translation: ``./run_tests.sh --pseudo de``
#. Compile the catalog: ``./run_tests.sh --compilemessages``
#. Run your development server.
#. Log in and change to the language you pseudo translated.
It should look weird. More specifically, the translatable segments are going
to start and end with a bracket and they are going to have some added
characters. For example, "Log In" will become "[~Log In~您好яшçあ]"
This is useful because you can inspect for the following, and consider if your
code is working like it should:
* If you see a string in English it's not translatable. Should it be?
* If you see brackets next to each other that might be concatenation. Concatenation
can make quality translations difficult or impossible. See
https://wiki.openstack.org/wiki/I18n/TranslatableStrings#Use_string_formating_variables.2C_never_perform_string_concatenation
for additional information.
* If there is unexpected wrapping/truncation there might not be enough
space for translations.
* If you see a string in the proper translated language, it comes from an
external source. (That's not bad, just sometimes useful to know)
* If you get new crashes, there is probably a bug.
Don't forget to cleanup any pseudo translated po files. Those don't get merged!
Code Style
==========
@ -180,7 +217,7 @@ Required
fragment and then append the fragment to the DOM in one pass instead of doing
multiple smaller DOM updates.
* Use “strict”, enclosing each JavaScript file inside a self-executing
function. The self-executing function keeps the strict scoped to the file,
function. The self-executing function keeps the strict scoped to the file,
so its variables and methods are not exposed to other JavaScript files in
the product.
@ -256,7 +293,7 @@ Required
elements if dynamic content is required.
3. Avoid using classes for detection purposes only, instead, defer to
attributes. For example to find a div:
attributes. For example to find a div:
.. code ::
<div class="something"></div>
@ -322,7 +359,7 @@ Required
* Controllers and Services should not contain DOM references. Directives
should.
* Services are singletons and contain logic independent of view.
* Scope is not the model (model is your JavaScript Objects). The scope
* Scope is not the model (model is your JavaScript Objects). The scope
references the model.
* Read-only in templates.
@ -349,7 +386,7 @@ Required
ways to do it.
* Using ``gettext`` or ``ngettext`` function that is passed from server to
client. If you're only translating a few things, this methodology is ok
client. If you're only translating a few things, this methodology is ok
to use.
* Use an Angular directive that will fetch a django template instead of a

@ -18,6 +18,7 @@ function usage {
echo " --makemessages Create/Update English translation files."
echo " --compilemessages Compile all translation files."
echo " --check-only Do not update translation files (--makemessages only)."
echo " --pseudo Pseudo translate a language."
echo " -p, --pep8 Just run pep8"
echo " -8, --pep8-changed [<basecommit>]"
echo " Just run PEP8 and HACKING compliance check"
@ -83,6 +84,7 @@ with_coverage=0
makemessages=0
compilemessages=0
check_only=0
pseudo=0
manage=0
# Jenkins sets a "JOB_NAME" variable, if it's not set, we'll make it "default"
@ -112,6 +114,7 @@ function process_option {
--makemessages) makemessages=1;;
--compilemessages) compilemessages=1;;
--check-only) check_only=1;;
--pseudo) pseudo=1;;
--only-selenium) only_selenium=1;;
--with-selenium) with_selenium=1;;
--selenium-headless) selenium_headless=1;;
@ -444,6 +447,17 @@ function run_compilemessages {
exit $(($HORIZON_PY_RESULT || $DASHBOARD_RESULT))
}
function run_pseudo {
for lang in $testargs
# Use English po file as the source file/pot file just like real Horizon translations
do
${command_wrapper} $root/tools/pseudo.py openstack_dashboard/locale/en/LC_MESSAGES/django.po openstack_dashboard/locale/$lang/LC_MESSAGES/django.po $lang
${command_wrapper} $root/tools/pseudo.py horizon/locale/en/LC_MESSAGES/django.po horizon/locale/$lang/LC_MESSAGES/django.po $lang
${command_wrapper} $root/tools/pseudo.py horizon/locale/en/LC_MESSAGES/djangojs.po horizon/locale/$lang/LC_MESSAGES/djangojs.po $lang
done
exit $?
}
# ---------PREPARE THE ENVIRONMENT------------ #
@ -511,6 +525,12 @@ if [ $compilemessages -eq 1 ]; then
exit $?
fi
# Generate Pseudo translation
if [ $pseudo -eq 1 ]; then
run_pseudo
exit $?
fi
# PEP8
if [ $just_pep8 -eq 1 ]; then
run_pep8

79
tools/pseudo.py Executable file

@ -0,0 +1,79 @@
#!/usr/bin/env python
# coding: utf-8
# Copyright 2015 IBM Corp.
# 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 argparse
import babel.messages.catalog as catalog
import babel.messages.pofile as pofile
def translate(segment):
prefix = u""
# When the id starts with a newline the mo compiler enforces that
# the translated message must also start with a newline. Make
# sure that doesn't get broken when prepending the bracket.
if segment.startswith('\n'):
prefix = u"\n"
orig_size = len(segment)
# Add extra expansion space based on recommenation from
# http://www-01.ibm.com/software/globalization/guidelines/a3.html
if orig_size < 20:
multiplier = 1
elif orig_size < 30:
multiplier = 0.8
elif orig_size < 50:
multiplier = 0.6
elif orig_size < 70:
multiplier = 0.4
else:
multiplier = 0.3
extra_length = int(max(0, (orig_size * multiplier) - 10))
extra_chars = "~" * extra_length
return u"{0}[~{1}~您好яшçあ{2}]".format(prefix, segment, extra_chars)
def main():
# Check arguments
parser = argparse.ArgumentParser()
parser.add_argument('pot_filename', type=argparse.FileType('r'))
parser.add_argument('po_filename', type=argparse.FileType('w'))
parser.add_argument('locale')
args = parser.parse_args()
# read POT file
pot_cat = pofile.read_po(args.pot_filename, ignore_obsolete=True)
# Create the new Catalog
new_cat = catalog.Catalog(locale=args.locale,
last_translator="pseudo.py",
charset="utf-8")
num_plurals = new_cat.num_plurals
# Process messages from template
for msg in pot_cat:
if msg.pluralizable:
msg.string = [translate(u"{}:{}".format(i, msg.id[0]))
for i in range(num_plurals)]
else:
msg.string = translate(msg.id)
new_cat[msg.id] = msg
# Write "translated" PO file
pofile.write_po(args.po_filename, new_cat, ignore_obsolete=True)
if __name__ == '__main__':
main()