Часть 1.
Изначально хотелось написать побольше упомянув в статье роуты и борьбу с ошибками, но тема интернационализации довольно обширна оказалась поэтому было решено посвятить пост исключительно ей. Мы будем пользоваться распространенным инструментом gettext в рамках фреймворка.
Все переводы можно разделить на две части:
a) Те что непосредственно в файлах с расширением .py — с ними все просто.
b) И те что в шаблонах — к ним придется написать небольшой велосипед :).
Ну и далее нужно автоматизировать процесс управления переводами.
Итак добро пожаловать под кат.
Для того чтоб функионировала система перевода мы импортируем функцию которая занимается переводом, в качестве аргумента передаем в нее название модуля в котором осуществляем перевод и все. Далее просто вызываем эту функцию как _() и передаем как аргумент переводимую фразу.
Вставляем в функцию render_templ, которая занимается рендерингом шаблонов, следующий фрагмент для передачи переводящей функции в шаблоны:
В итоге получаем:
Также в шаблоны можно передать контекст окружения, чтоб не передавать его в каждой функции.
Дальше идут функции, которые занимаются переводами.
Теперь нам предстоит автоматизировать перевод после того как мы написали в шаблоне {{_('text')}}. Мы должны получить в папках /app/module/ru/LS_MESSAGES файлики _.mo, _.po.
_.mo — скомпилированный файлик откуда gettext потом читает переводы.
_.po — файл с исходниками для переводчиков по такому образцу:
Работа с этими файлами ведется стандартными командами из консоли:
xgettext – собирает по файлам строчки д��я перевода.
msginit – создает файл перевода для конкретного языка _.po.
msgfmt – компилирует в бинарный файл _.mo.
msgmerge – обновление файлов переводов.
Но во первых хотелось бы все автоматизировать и не писать каждый раз несколько команд, а во вторых xgettext не умеет работать с шаблонами а только с файлами *.py по крайней мере такая возможность у него найдена не была.
Поэтому мы напишем небольшую утилиту которая одной командой выполняла бы за нас все эти действия.
Теперь в фреймворке есть модульная структура, шаблонизация, и интернализация. Для создания работоспособного каркаса нам осталось добавить отладку, работу с роутами и статикой. После этого можно будет приступать к созданию основных компонентов: админка, представление данных и тд.
Пока все.
О природе gettext
Введение в gettext в python
gettext и jinja2
Документация по python и gettext
Продолжение
Введение.
Изначально хотелось написать побольше упомянув в статье роуты и борьбу с ошибками, но тема интернационализации довольно обширна оказалась поэтому было решено посвятить пост исключительно ей. Мы будем пользоваться распространенным инструментом gettext в рамках фреймворка.
Все переводы можно разделить на две части:
a) Те что непосредственно в файлах с расширением .py — с ними все просто.
b) И те что в шаблонах — к ним придется написать небольшой велосипед :).
Ну и далее нужно автоматизировать процесс управления переводами.
Итак добро пожаловать под кат.
Перевод фраз из питоновских файлов
Для того чтоб функионировала система перевода мы импортируем функцию которая занимается переводом, в качестве аргумента передаем в нее название модуля в котором осуществляем перевод и все. Далее просто вызываем эту функцию как _() и передаем как аргумент переводимую фразу.
import core.union; _ = core.union.get_trans('module') _('text')
Перевод фраз из шаблонов
Вставляем в функцию render_templ, которая занимается рендерингом шаблонов, следующий фрагмент для передачи переводящей функции в шаблоны:
p['gettext'] = get_trans(module) p['_'] = get_trans(module)
В итоге получаем:
def render_templ(template, **kwarg): template = jinja.get_template(template) module = split_templ_name( template)[0] kwarg['gettext'] = get_trans(module) kwarg['_'] = get_trans(module) kwarg['context'] = context() return template.render(**kwarg)
Также в шаблоны можно передать контекст окружения, чтоб не передавать его в каждой функции.
Дальше идут функции, которые занимаются переводами.
langs = {} def load_lng(path, module_name, lang): """ Подгружает модули с языками """ if not module_name in langs[lang]: langs[lang][module_name] = [] path = os.path.join( path, module_name, 'locale') if module_name else os.path.join (path, 'locale') if os.path.isdir(path): t = gettext.translation('_', path, [cur_lang()], codeset='UTF-8') langs[lang][module_name].append(t) def get_lng(module): """ Возвращает объекты перевода для компонента. """ lang = cur_lang() if not lang in langs: langs[lang] = {} if not module in langs[lang]: langs[lang][module] = [] load_lng(os.path.join (settings.lib_path,'app'), module, lang) load_lng(os.path.join (os.getcwd(),'app'), module, lang) if not module: load_lng(os.path.join (os.getcwd()), '', lang) return langs[lang][module] def trans(module, s): """ принимает имя компонента и строчку, которую надо перевести и непосредственно переводит""" if type(s) == str: s = s.decode('UTF-8') translated = s lng = get_lng(module) if lng: for i in reversed(lng): translated = i.gettext(s).decode('UTF-8') # если удалось перевести то translated отличается от оригинала и дальше не надо искать. if s != translated: break return translated def get_trans(module): # возвращает функцию которая переводит саму фразу return lambda s: trans(module, s)
Работа с gettext
Теперь нам предстоит автоматизировать перевод после того как мы написали в шаблоне {{_('text')}}. Мы должны получить в папках /app/module/ru/LS_MESSAGES файлики _.mo, _.po.
_.mo — скомпилированный файлик откуда gettext потом читает переводы.
_.po — файл с исходниками для переводчиков по такому образцу:
#: /path/modul/templ/base.tpl:25 msgid "text" msgstr "" # сюда текст перевода вписывают уже переводчики.
Работа с этими файлами ведется стандартными командами из консоли:
xgettext – собирает по файлам строчки д��я перевода.
msginit – создает файл перевода для конкретного языка _.po.
msgfmt – компилирует в бинарный файл _.mo.
msgmerge – обновление файлов переводов.
Но во первых хотелось бы все автоматизировать и не писать каждый раз несколько команд, а во вторых xgettext не умеет работать с шаблонами а только с файлами *.py по крайней мере такая возможность у него найдена не была.
Поэтому мы напишем небольшую утилиту которая одной командой выполняла бы за нас все эти действия.
# Список языков которые собираемся подерживать. list_lang = ['ru_RU', 'en_US'] Получение аргументов из командной строки. s = str(sys.argv) s = s[1:-1]; app = [] for word in s.split(", "): app.append(word) # путь к папке с фреймворком lib_path = '/path' # путь к каталогу с сайтом app_path = app[1][1:-1] def iter_trans(dir, is_app=True): """ Идем по компонентам ищем там локаль и превращаем файлик с переводами в бинарник.""" if is_app: #тут бежим по компонентам for res in os.listdir(dir): path_ = os.path.join(dir, res, 'locale') if os.path.isdir(path_): for res in os.listdir(path_): path = os.path.join(path_, res, 'LC_MESSAGES') if os.path.isdir(path): po_f = os.path.join(path, '_.po') mo_f = os.path.join(path, '_.mo') os.popen("msgfmt -o %s %s" % (mo_f, po_f )).read() else: # тут идем по переводам из проекта. path_ = os.path.join(dir, 'locale') if os.path.isdir(path_): for res in os.listdir(path_): path = os.path.join(path_, res, 'LC_MESSAGES') if os.path.isdir(path): po_f = os.path.join(path, '_.po') mo_f = os.path.join(path, '_.mo') os.popen("msgfmt -o %s %s" % (mo_f, po_f )).read() def iter_mo(dir, is_app=True): """ Идем по шаблонам """ if is_app: for res in os.listdir(dir): path = os.path.join(dir, res, 'templ') if os.path.isdir(path): iter_templ(path) else: path = os.path.join(dir, 'templ') if os.path.isdir(path): iter_templ(path) pot_header = """# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\\n" "Report-Msgid-Bugs-To: \\n" "POT-Creation-Date: %s\\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n" "Language-Team: LANGUAGE <LL@li.org>\\n" "MIME-Version: 1.0\\n" "Content-Type: text/plain; charset=UTF-8\\n" "Content-Transfer-Encoding: 8bit\\n" """ % (time.strftime("%Y-%m-%d %H:%M%z")) def iter_templ(dir): """ Из шаблонов извлекаем строчки для переводов и закидуем их в pot файлы. А затем создаем по списку языков необходимые _.po файлы или сливаем с уже существующими. """ out_f = os.path.join(dir, '..', 'locale', '_.pot') file_o = open(out_f, 'w') # Записываем в файл заголовок. file_o.write(pot_header) for name in os.listdir(dir): if name.endswith('.tpl'): load_translation(os.path.join(dir, name), file_o) file_o.close() for res in list_lang: lang = res[:2] po_path = os.path.join(dir, '..', 'locale', lang) if not os.path.isdir(po_path): os.makedirs(po_path, 0755) po_path = os.path.join(po_path, 'LC_MESSAGES') if not os.path.isdir(po_path): os.makedirs(po_path, 0755) po_f = os.path.join(po_path, '_.po') if not os.path.isfile(po_f): os.popen("msginit --no-translator -i %s -o %s -l %s" % ( out_f, po_f, res+'.UTF-8')).read() else: os.popen("msgmerge %s %s -o %s" % (po_f, out_f, po_f)).read() def load_translation(in_f, file): """ Извлекаем строки из шаблона и записуем их в файл. """ with open(in_f, 'r') as f: l = f.read().split('\n') n = 0; r = {} for rs in l: n += 1 # находим подчеркивание со скобочками aa = re.findall(r'_\([^)]+\)', rs) for res in aa: # вырезаем саму строчку без подчеркивания скобок и кавычек res = res[3:-2] # смотрим нет ли еще такой строчки if not res in r: r[res] = [] # Добавляем номер строки r[res].append(n) for res, nums in r.iteritems(): file.write('#: '+in_f+':'+','.join([str(x) for x in nums])+'\n') file.write('msgid "'+res+'"\n') file.write('msgstr ""\n\n') # теперь если у нас стоит аргумент 'cpl', то компилируем, если нет, то просто собираем строчки. itr = iter_trans if len(app) > 2 and app[2][1:-1] == 'cpl' else iter_mo for res in [lib_path + '/', False, lib_path +'/app/', app_path +'/app/', app_path + '/', False]: itr(res)
Резюме
Теперь в фреймворке есть модульная структура, шаблонизация, и интернализация. Для создания работоспособного каркаса нам осталось добавить отладку, работу с роутами и статикой. После этого можно будет приступать к созданию основных компонентов: админка, представление данных и тд.
Пока все.
Используемые материалы
О природе gettext
Введение в gettext в python
gettext и jinja2
Документация по python и gettext
Продолжение