
User experience =============== For users it means we can provide localized web-sites to enrich their overall experiences. Although for the Blender Cloud this doesn't make much sense (since the content is in English), Flamenco and Attract can really benefit from this. New configuration settings ========================== There are two new parameters in config.py: * DEFAULT_LOCALE='en_US' * SUPPORT_ENGLISH=True They are both properly documented in the `config.py` file. Technicall details ================== We are using the 'Accept-Languages' header to match the available translations with the user supported languages. If an extension has a `translations` folder, it's used for translations. However the main application (e.g., Blender Cloud) is the one that determines the supported languages based on its `languages` folder. How to mark strings for translation =================================== See the documentation in README.md. But as an example, 404.pug and pillar/__init__.py::handle_sdk_resource_invalid have marked up strings that will be extracted once you install pillar, or run any of the translations commangs. Remember to **gulp** after you update the template files. How to setup translations ========================= You will need to create translation for the main project, and for each extension that you want to see translated. I added a new entry-point to the installation of Pillar. So all you need is to use the `translations` script to initialize, update and compile your translations. Pending tasks ============= Aside from marking more strings for extraction and start the translation effort it would be interesting to replace the pretty_date routine with momentjs. Acknowledgement =============== Many thanks for Sybren Stüvel for the suggestions and throughout code review. Thanks also to Francesco Siddi for the original documentation and suggesting me to tackle this. And Kudos for Pablo Vazquez for the motivational support and for the upcoming "strings mark up" task force! The core of the implementation is based on Miguel Grinberg i18n chapter of his great 'The Mega Flask Tutorial'. Reviewers: sybren Differential Revision: https://developer.blender.org/D2826
105 lines
2.6 KiB
Python
105 lines
2.6 KiB
Python
import argparse
|
|
import contextlib
|
|
import pathlib
|
|
import subprocess
|
|
import sys
|
|
|
|
BABEL_CONFIG = pathlib.Path('translations.cfg')
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def create_messages_pot() -> pathlib.Path:
|
|
"""Extract the translatable strings from the source code
|
|
|
|
This creates a temporary messages.pot file, to be used to init or
|
|
update the translation .mo files.
|
|
|
|
It works as a generator, yielding the temporarily created pot file.
|
|
The messages.pot file will be deleted at the end of it if all went well.
|
|
|
|
:return The path of the messages.pot file created.
|
|
"""
|
|
if not BABEL_CONFIG.is_file():
|
|
print("No translations config file found: %s" % (BABEL_CONFIG))
|
|
sys.exit(-1)
|
|
return
|
|
|
|
messages_pot = pathlib.Path('messages.pot')
|
|
subprocess.run(('pybabel', 'extract', '-F', BABEL_CONFIG, '-k', 'lazy_gettext', '-o', messages_pot, '.'))
|
|
yield messages_pot
|
|
messages_pot.unlink()
|
|
|
|
|
|
def init(locale):
|
|
"""
|
|
Initialize the translations for a new language.
|
|
"""
|
|
with create_messages_pot() as messages_pot:
|
|
subprocess.run(('pybabel', 'init', '-i', messages_pot, '-d', 'translations', '-l', locale))
|
|
|
|
|
|
def update():
|
|
"""
|
|
Update the strings to be translated.
|
|
"""
|
|
with create_messages_pot() as messages_pot:
|
|
subprocess.run(('pybabel', 'update', '-i', messages_pot, '-d', 'translations'))
|
|
|
|
|
|
def compile():
|
|
"""
|
|
Compile the translation to be used.
|
|
"""
|
|
if pathlib.Path('translations').is_dir():
|
|
subprocess.run(('pybabel', 'compile','-d', 'translations'))
|
|
else:
|
|
print("No translations folder available")
|
|
|
|
|
|
def parse_arguments() -> argparse.Namespace:
|
|
"""
|
|
Parse command-line arguments.
|
|
"""
|
|
parser = argparse.ArgumentParser(description='Translate Pillar')
|
|
|
|
parser.add_argument(
|
|
'mode',
|
|
type=str,
|
|
help='Init once, update often, compile before deploying.',
|
|
choices=['init', 'update', 'compile'])
|
|
|
|
parser.add_argument(
|
|
'languages',
|
|
nargs='*',
|
|
type=str,
|
|
help='Languages to initialize: pt it es ...')
|
|
|
|
args = parser.parse_args()
|
|
if args.mode == 'init' and not args.languages:
|
|
parser.error("init requires languages")
|
|
|
|
return args
|
|
|
|
|
|
def main():
|
|
"""
|
|
When calling from the setup.py entry-point we need to parse the arguments
|
|
and init/update/compile the translations strings
|
|
"""
|
|
args = parse_arguments()
|
|
|
|
if args.mode == 'init':
|
|
for language in args.languages:
|
|
init(language)
|
|
|
|
elif args.mode == 'update':
|
|
update()
|
|
|
|
else: # mode == 'compile'
|
|
compile()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
|