[MERGE] Unify PO extraction system between server/addons and web + minor cleanup
bzr revid: odo@openerp.com-20121004074420-c0b2xxxyn5etto2i
This commit is contained in:
commit
aa6672b2af
|
@ -44,6 +44,7 @@ The kernel of OpenERP, needed for all installation.
|
|||
'data/res.country.state.csv',
|
||||
'ir/wizard/wizard_menu_view.xml',
|
||||
'ir/ir.xml',
|
||||
'ir/ir_translation_view.xml',
|
||||
'ir/ir_filters.xml',
|
||||
'ir/ir_config_parameter_view.xml',
|
||||
'ir/workflow/workflow_view.xml',
|
||||
|
|
|
@ -1147,75 +1147,6 @@
|
|||
<menuitem action="action_model_relation" id="ir_model_relation_menu" parent="base.next_id_9"
|
||||
groups="base.group_no_one"/>
|
||||
|
||||
<!-- Translations -->
|
||||
|
||||
<record id="view_translation_search" model="ir.ui.view">
|
||||
<field name="name">Translations</field>
|
||||
<field name="model">ir.translation</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Translations">
|
||||
<filter icon="terp-gdu-smart-failing"
|
||||
string="Untranslated"
|
||||
domain="['|',('value', '=', False),('value','=','')]"/>
|
||||
<field name="name" operator="="/>
|
||||
<field name="lang"/>
|
||||
<field name="src"/>
|
||||
<field name="value"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_translation_form" model="ir.ui.view">
|
||||
<field name="name">Translations</field>
|
||||
<field name="model">ir.translation</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Translations" version="7.0">
|
||||
<header>
|
||||
<field name="state" widget="statusbar" nolabel="1"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="lang"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="type"/>
|
||||
<field name="res_id"/>
|
||||
</group>
|
||||
<group string="Source Term">
|
||||
<field name="src" nolabel="1" height="400"/>
|
||||
</group>
|
||||
<group string="Translation">
|
||||
<field name="value" nolabel="1" height="400"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_translation_tree" model="ir.ui.view">
|
||||
<field name="name">Translations</field>
|
||||
<field name="model">ir.translation</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Translations" editable="bottom">
|
||||
<field name="src" readonly="True"/>
|
||||
<field name="value"/>
|
||||
<field name="name" readonly="True"/>
|
||||
<field name="lang" readonly="True"/>
|
||||
<field name="type" readonly="True"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_translation" model="ir.actions.act_window">
|
||||
<field name="name">Translated Terms</field>
|
||||
<field name="res_model">ir.translation</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_translation_tree"/>
|
||||
</record>
|
||||
<menuitem action="action_translation" id="menu_action_translation" parent="base.menu_translation_app" />
|
||||
|
||||
<!--
|
||||
=============================================================
|
||||
Menu Edition
|
||||
|
|
|
@ -19,10 +19,12 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from osv import fields, osv
|
||||
import tools
|
||||
import logging
|
||||
|
||||
import openerp.modules
|
||||
from openerp.osv import fields, osv
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
TRANSLATION_TYPE = [
|
||||
|
@ -57,7 +59,6 @@ class ir_translation_import_cursor(object):
|
|||
the data.
|
||||
@param parent an instance of ir.translation ORM model
|
||||
"""
|
||||
|
||||
self._cr = cr
|
||||
self._uid = uid
|
||||
self._context = context
|
||||
|
@ -67,29 +68,23 @@ class ir_translation_import_cursor(object):
|
|||
|
||||
# Note that Postgres will NOT inherit the constraints or indexes
|
||||
# of ir_translation, so this copy will be much faster.
|
||||
|
||||
cr.execute('''CREATE TEMP TABLE %s(
|
||||
imd_model VARCHAR(64),
|
||||
imd_module VARCHAR(64),
|
||||
imd_name VARCHAR(128)
|
||||
) INHERITS (%s) ''' % (self._table_name, self._parent_table))
|
||||
|
||||
def push(self, ddict):
|
||||
def push(self, trans_dict):
|
||||
"""Feed a translation, as a dictionary, into the cursor
|
||||
"""
|
||||
state = "translated" if (ddict['value'] and ddict['value'] != "") else "to_translate"
|
||||
self._cr.execute("INSERT INTO " + self._table_name \
|
||||
+ """(name, lang, res_id, src, type,
|
||||
imd_model, imd_module, imd_name, value,state)
|
||||
VALUES(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
|
||||
(ddict['name'], ddict['lang'], ddict.get('res_id'), ddict['src'], ddict['type'],
|
||||
ddict.get('imd_model'), ddict.get('imd_module'), ddict.get('imd_name'),
|
||||
ddict['value'],state))
|
||||
params = dict(trans_dict, state="translated" if trans_dict['value'] else "to_translate")
|
||||
self._cr.execute("""INSERT INTO %s (name, lang, res_id, src, type, imd_model, module, imd_name, value, state, comments)
|
||||
VALUES (%%(name)s, %%(lang)s, %%(res_id)s, %%(src)s, %%(type)s, %%(imd_model)s, %%(module)s,
|
||||
%%(imd_name)s, %%(value)s, %%(state)s, %%(comments)s)""" % self._table_name,
|
||||
params)
|
||||
|
||||
def finish(self):
|
||||
""" Transfer the data from the temp table to ir.translation
|
||||
"""
|
||||
|
||||
cr = self._cr
|
||||
if self._debug:
|
||||
cr.execute("SELECT count(*) FROM %s" % self._table_name)
|
||||
|
@ -101,22 +96,21 @@ class ir_translation_import_cursor(object):
|
|||
SET res_id = imd.res_id
|
||||
FROM ir_model_data AS imd
|
||||
WHERE ti.res_id IS NULL
|
||||
AND ti.imd_module IS NOT NULL AND ti.imd_name IS NOT NULL
|
||||
AND ti.module IS NOT NULL AND ti.imd_name IS NOT NULL
|
||||
|
||||
AND ti.imd_module = imd.module AND ti.imd_name = imd.name
|
||||
AND ti.module = imd.module AND ti.imd_name = imd.name
|
||||
AND ti.imd_model = imd.model; """ % self._table_name)
|
||||
|
||||
if self._debug:
|
||||
cr.execute("SELECT imd_module, imd_model, imd_name FROM %s " \
|
||||
"WHERE res_id IS NULL AND imd_module IS NOT NULL" % self._table_name)
|
||||
cr.execute("SELECT module, imd_model, imd_name FROM %s " \
|
||||
"WHERE res_id IS NULL AND module IS NOT NULL" % self._table_name)
|
||||
for row in cr.fetchall():
|
||||
_logger.debug("ir.translation.cursor: missing res_id for %s. %s/%s ", *row)
|
||||
|
||||
cr.execute("DELETE FROM %s WHERE res_id IS NULL AND imd_module IS NOT NULL" % \
|
||||
self._table_name)
|
||||
|
||||
# Records w/o res_id must _not_ be inserted into our db, because they are
|
||||
# referencing non-existent data.
|
||||
cr.execute("DELETE FROM %s WHERE res_id IS NULL AND module IS NOT NULL" % \
|
||||
self._table_name)
|
||||
|
||||
find_expr = "irt.lang = ti.lang AND irt.type = ti.type " \
|
||||
" AND irt.name = ti.name AND irt.src = ti.src " \
|
||||
|
@ -126,15 +120,14 @@ class ir_translation_import_cursor(object):
|
|||
if self._overwrite:
|
||||
cr.execute("""UPDATE ONLY %s AS irt
|
||||
SET value = ti.value,
|
||||
state = 'translated'
|
||||
state = 'translated'
|
||||
FROM %s AS ti
|
||||
WHERE %s AND ti.value IS NOT NULL AND ti.value != ''
|
||||
""" % (self._parent_table, self._table_name, find_expr))
|
||||
|
||||
# Step 3: insert new translations
|
||||
|
||||
cr.execute("""INSERT INTO %s(name, lang, res_id, src, type, value,state)
|
||||
SELECT name, lang, res_id, src, type, value,state
|
||||
cr.execute("""INSERT INTO %s(name, lang, res_id, src, type, value, module, state, comments)
|
||||
SELECT name, lang, res_id, src, type, value, module, state, comments
|
||||
FROM %s AS ti
|
||||
WHERE NOT EXISTS(SELECT 1 FROM ONLY %s AS irt WHERE %s);
|
||||
""" % (self._parent_table, self._table_name, self._parent_table, find_expr))
|
||||
|
@ -162,26 +155,37 @@ class ir_translation(osv.osv):
|
|||
return [(d['code'], d['name']) for d in lang_data]
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Field Name', size=128, required=True),
|
||||
'res_id': fields.integer('Resource ID', select=True),
|
||||
'lang': fields.selection(_get_language, string='Language', size=16),
|
||||
'type': fields.selection(TRANSLATION_TYPE, string='Type', size=16, select=True),
|
||||
'name': fields.char('Translated field', required=True),
|
||||
'res_id': fields.integer('Record ID', select=True),
|
||||
'lang': fields.selection(_get_language, string='Language'),
|
||||
'type': fields.selection(TRANSLATION_TYPE, string='Type', select=True),
|
||||
'src': fields.text('Source'),
|
||||
'value': fields.text('Translation Value'),
|
||||
'state':fields.selection([('to_translate','To Translate'),('inprogress','Translation in Progress'),('translated','Translated')])
|
||||
'module': fields.char('Module', help="Module this term belongs to", select=True),
|
||||
|
||||
'state': fields.selection(
|
||||
[('to_translate','To Translate'),
|
||||
('inprogress','Translation in Progress'),
|
||||
('translated','Translated')],
|
||||
string="State",
|
||||
help="Automatically set to let administators find new terms that might need to be translated"),
|
||||
|
||||
# aka gettext extracted-comments - we use them to flag openerp-web translation
|
||||
# cfr: http://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/PO-Files.html
|
||||
'comments': fields.text('Translation comments', select=True),
|
||||
}
|
||||
|
||||
|
||||
_defaults = {
|
||||
'state':'to_translate',
|
||||
'state': 'to_translate',
|
||||
}
|
||||
|
||||
_sql_constraints = [ ('lang_fkey_res_lang', 'FOREIGN KEY(lang) REFERENCES res_lang(code)',
|
||||
|
||||
_sql_constraints = [ ('lang_fkey_res_lang', 'FOREIGN KEY(lang) REFERENCES res_lang(code)',
|
||||
'Language code of translation item must be among known languages' ), ]
|
||||
|
||||
def _auto_init(self, cr, context=None):
|
||||
super(ir_translation, self)._auto_init(cr, context)
|
||||
|
||||
# FIXME: there is a size limit on btree indexed values so we can't index src column with normal btree.
|
||||
# FIXME: there is a size limit on btree indexed values so we can't index src column with normal btree.
|
||||
cr.execute('SELECT indexname FROM pg_indexes WHERE indexname = %s', ('ir_translation_ltns',))
|
||||
if cr.fetchone():
|
||||
#temporarily removed: cr.execute('CREATE INDEX ir_translation_ltns ON ir_translation (name, lang, type, src)')
|
||||
|
@ -207,7 +211,7 @@ class ir_translation(osv.osv):
|
|||
if field == 'lang':
|
||||
return
|
||||
return super(ir_translation, self)._check_selection_field_value(cr, uid, field, value, context=context)
|
||||
|
||||
|
||||
@tools.ormcache_multi(skiparg=3, multi=6)
|
||||
def _get_ids(self, cr, uid, name, tt, lang, ids):
|
||||
translations = dict.fromkeys(ids, False)
|
||||
|
@ -271,10 +275,10 @@ class ir_translation(osv.osv):
|
|||
if isinstance(types, basestring):
|
||||
types = (types,)
|
||||
if source:
|
||||
query = """SELECT value
|
||||
FROM ir_translation
|
||||
WHERE lang=%s
|
||||
AND type in %s
|
||||
query = """SELECT value
|
||||
FROM ir_translation
|
||||
WHERE lang=%s
|
||||
AND type in %s
|
||||
AND src=%s"""
|
||||
params = (lang or '', types, tools.ustr(source))
|
||||
if name:
|
||||
|
@ -308,9 +312,9 @@ class ir_translation(osv.osv):
|
|||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
if vals.get('src') or ('value' in vals and not(vals.get('value'))):
|
||||
result = vals.update({'state':'to_translate'})
|
||||
vals.update({'state':'to_translate'})
|
||||
if vals.get('value'):
|
||||
result = vals.update({'state':'translated'})
|
||||
vals.update({'state':'translated'})
|
||||
result = super(ir_translation, self).write(cursor, user, ids, vals, context=context)
|
||||
for trans_obj in self.read(cursor, user, ids, ['name','type','res_id','src','lang'], context=context):
|
||||
self._get_source.clear_cache(self, user, trans_obj['name'], trans_obj['type'], trans_obj['lang'], trans_obj['src'])
|
||||
|
@ -379,7 +383,34 @@ class ir_translation(osv.osv):
|
|||
"""
|
||||
return ir_translation_import_cursor(cr, uid, self, context=context)
|
||||
|
||||
ir_translation()
|
||||
def load(self, cr, modules, langs, context=None):
|
||||
context = dict(context or {}) # local copy
|
||||
for module_name in modules:
|
||||
modpath = openerp.modules.get_module_path(module_name)
|
||||
if not modpath:
|
||||
continue
|
||||
for lang in langs:
|
||||
lang_code = tools.get_iso_codes(lang)
|
||||
base_lang_code = None
|
||||
if '_' in lang_code:
|
||||
base_lang_code = lang_code.split('_')[0]
|
||||
|
||||
# Step 1: for sub-languages, load base language first (e.g. es_CL.po is loaded over es.po)
|
||||
if base_lang_code:
|
||||
base_trans_file = openerp.modules.get_module_resource(module_name, 'i18n', base_lang_code + '.po')
|
||||
if base_trans_file:
|
||||
_logger.info('module %s: loading base translation file %s for language %s', module_name, base_lang_code, lang)
|
||||
tools.trans_load(cr, base_trans_file, lang, verbose=False, module_name=module_name, context=context)
|
||||
context['overwrite'] = True # make sure the requested translation will override the base terms later
|
||||
|
||||
# Step 2: then load the main translation file, possibly overriding the terms coming from the base language
|
||||
trans_file = openerp.modules.get_module_resource(module_name, 'i18n', lang_code + '.po')
|
||||
if trans_file:
|
||||
_logger.info('module %s: loading translation file (%s) for language %s', module_name, lang_code, lang)
|
||||
tools.trans_load(cr, trans_file, lang, verbose=False, module_name=module_name, context=context)
|
||||
elif lang_code != 'en':
|
||||
_logger.warning('module %s: no translation for language %s', module_name, lang_code)
|
||||
return True
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<openerp>
|
||||
<data>
|
||||
<!-- Translations -->
|
||||
<record id="view_translation_search" model="ir.ui.view">
|
||||
<field name="name">Translations</field>
|
||||
<field name="model">ir.translation</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Translations">
|
||||
<filter icon="terp-gdu-smart-failing"
|
||||
string="Untranslated"
|
||||
domain="['|',('value', '=', False),('value','=','')]"/>
|
||||
<filter name="openerp-web"
|
||||
string="Web-only translations"
|
||||
domain="[('comments', 'like', 'openerp-web')]"/>
|
||||
<field name="name" operator="="/>
|
||||
<field name="lang"/>
|
||||
<field name="src"/>
|
||||
<field name="value"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_translation_form" model="ir.ui.view">
|
||||
<field name="name">Translations</field>
|
||||
<field name="model">ir.translation</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Translations" version="7.0">
|
||||
<header>
|
||||
<field name="state" widget="statusbar" nolabel="1"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="lang"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="type"/>
|
||||
<field name="res_id"/>
|
||||
</group>
|
||||
<group string="Source Term">
|
||||
<field name="src" nolabel="1" height="400"/>
|
||||
</group>
|
||||
<group string="Translation">
|
||||
<field name="value" nolabel="1" height="400"/>
|
||||
</group>
|
||||
<group string="Comments">
|
||||
<field name="comments" nolabel="1" height="100"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_translation_tree" model="ir.ui.view">
|
||||
<field name="name">Translations</field>
|
||||
<field name="model">ir.translation</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Translations" editable="bottom">
|
||||
<field name="src" readonly="True"/>
|
||||
<field name="value"/>
|
||||
<field name="name" readonly="True"/>
|
||||
<field name="lang" readonly="True"/>
|
||||
<field name="type" readonly="True"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_translation" model="ir.actions.act_window">
|
||||
<field name="name">Translated Terms</field>
|
||||
<field name="res_model">ir.translation</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_translation_tree"/>
|
||||
</record>
|
||||
|
||||
<menuitem action="action_translation" id="menu_action_translation" parent="base.menu_translation_app" />
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -19,6 +19,5 @@
|
|||
#
|
||||
##############################################################################
|
||||
import wizard_menu
|
||||
import wizard_screen
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
|
|
|
@ -47,25 +47,6 @@ class wizard_model_menu(osv.osv_memory):
|
|||
'icon': 'STOCK_INDENT'
|
||||
}, context)
|
||||
return {'type':'ir.actions.act_window_close'}
|
||||
wizard_model_menu()
|
||||
|
||||
class wizard_model_menu_line(osv.osv_memory):
|
||||
_name = 'wizard.ir.model.menu.create.line'
|
||||
_columns = {
|
||||
'wizard_id': fields.many2one('wizard.ir.model.menu.create','Wizard'),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'view_type': fields.selection([
|
||||
('tree','Tree'),
|
||||
('form','Form'),
|
||||
('graph','Graph'),
|
||||
('calendar','Calendar'),
|
||||
('gantt','Gantt')],'View Type',required=True),
|
||||
'view_id': fields.many2one('ir.ui.view', 'View'),
|
||||
}
|
||||
_defaults = {
|
||||
'view_type': lambda self,cr,uid,ctx: 'tree'
|
||||
}
|
||||
wizard_model_menu_line()
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# Copyright (C) 2010 OpenERP s.a. (<http://www.openerp.com>).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import base64
|
||||
import os
|
||||
import random
|
||||
|
||||
import tools
|
||||
from osv import fields,osv
|
||||
|
||||
# Simple base class for wizards that wish to use random images on the left
|
||||
# side of the form.
|
||||
class wizard_screen(osv.osv_memory):
|
||||
_name = 'ir.wizard.screen'
|
||||
|
||||
def _get_image(self, cr, uid, context=None):
|
||||
path = os.path.join('base','res','config_pixmaps','%d.png'%random.randrange(1,4))
|
||||
image_file = file_data = tools.file_open(path,'rb')
|
||||
try:
|
||||
file_data = image_file.read()
|
||||
return base64.encodestring(file_data)
|
||||
finally:
|
||||
image_file.close()
|
||||
|
||||
def _get_image_fn(self, cr, uid, ids, name, args, context=None):
|
||||
image = self._get_image(cr, uid, context)
|
||||
return dict.fromkeys(ids, image) # ok to use .fromkeys() as the image is same for all
|
||||
|
||||
_columns = {
|
||||
'config_logo': fields.function(_get_image_fn, string='Image', type='binary'),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'config_logo': _get_image
|
||||
}
|
||||
wizard_screen()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -627,45 +627,14 @@ class module(osv.osv):
|
|||
self.write(cr, uid, [mod_browse.id], {'category_id': p_id})
|
||||
|
||||
def update_translations(self, cr, uid, ids, filter_lang=None, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if not filter_lang:
|
||||
pool = pooler.get_pool(cr.dbname)
|
||||
lang_obj = pool.get('res.lang')
|
||||
lang_ids = lang_obj.search(cr, uid, [('translatable', '=', True)])
|
||||
filter_lang = [lang.code for lang in lang_obj.browse(cr, uid, lang_ids)]
|
||||
res_lang = self.pool.get('res.lang')
|
||||
lang_ids = res_lang.search(cr, uid, [('translatable', '=', True)])
|
||||
filter_lang = [lang.code for lang in res_lang.browse(cr, uid, lang_ids)]
|
||||
elif not isinstance(filter_lang, (list, tuple)):
|
||||
filter_lang = [filter_lang]
|
||||
|
||||
for mod in self.browse(cr, uid, ids):
|
||||
if mod.state != 'installed':
|
||||
continue
|
||||
modpath = modules.get_module_path(mod.name)
|
||||
if not modpath:
|
||||
# unable to find the module. we skip
|
||||
continue
|
||||
for lang in filter_lang:
|
||||
iso_lang = tools.get_iso_codes(lang)
|
||||
f = modules.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
|
||||
context2 = context and context.copy() or {}
|
||||
if f and '_' in iso_lang:
|
||||
iso_lang2 = iso_lang.split('_')[0]
|
||||
f2 = modules.get_module_resource(mod.name, 'i18n', iso_lang2 + '.po')
|
||||
if f2:
|
||||
_logger.info('module %s: loading base translation file %s for language %s', mod.name, iso_lang2, lang)
|
||||
tools.trans_load(cr, f2, lang, verbose=False, context=context)
|
||||
context2['overwrite'] = True
|
||||
# Implementation notice: we must first search for the full name of
|
||||
# the language derivative, like "en_UK", and then the generic,
|
||||
# like "en".
|
||||
if (not f) and '_' in iso_lang:
|
||||
iso_lang = iso_lang.split('_')[0]
|
||||
f = modules.get_module_resource(mod.name, 'i18n', iso_lang + '.po')
|
||||
if f:
|
||||
_logger.info('module %s: loading translation file (%s) for language %s', mod.name, iso_lang, lang)
|
||||
tools.trans_load(cr, f, lang, verbose=False, context=context2)
|
||||
elif iso_lang != 'en':
|
||||
_logger.warning('module %s: no translation for language %s', mod.name, iso_lang)
|
||||
modules = [m.name for m in self.browse(cr, uid, ids) if m.state == 'installed']
|
||||
self.pool.get('ir.translation').load(cr, modules, filter_lang, context=context)
|
||||
|
||||
def check(self, cr, uid, ids, context=None):
|
||||
for mod in self.browse(cr, uid, ids, context=context):
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2004-2012 OpenERP S.A. <http://openerp.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -22,68 +22,58 @@
|
|||
import tools
|
||||
import base64
|
||||
import cStringIO
|
||||
import pooler
|
||||
from osv import fields,osv
|
||||
from tools.translate import _
|
||||
from tools.misc import get_iso_codes
|
||||
|
||||
NEW_LANG_KEY = '__new__'
|
||||
|
||||
class base_language_export(osv.osv_memory):
|
||||
_name = "base.language.export"
|
||||
|
||||
def _get_languages(self, cr, uid, context):
|
||||
lang_obj=pooler.get_pool(cr.dbname).get('res.lang')
|
||||
ids=lang_obj.search(cr, uid, ['&', ('active', '=', True), ('translatable', '=', True),])
|
||||
langs=lang_obj.browse(cr, uid, ids)
|
||||
return [(lang.code, lang.name) for lang in langs]
|
||||
|
||||
def act_cancel(self, cr, uid, ids, context=None):
|
||||
#self.unlink(cr, uid, ids, context)
|
||||
return {'type':'ir.actions.act_window_close' }
|
||||
|
||||
def act_destroy(self, *args):
|
||||
return {'type':'ir.actions.act_window_close' }
|
||||
lang_obj = self.pool.get('res.lang')
|
||||
ids = lang_obj.search(cr, uid, [('translatable', '=', True)])
|
||||
langs = lang_obj.browse(cr, uid, ids)
|
||||
return [(NEW_LANG_KEY, _('New Language (Empty translation template)'))] + [(lang.code, lang.name) for lang in langs]
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('File Name', readonly=True),
|
||||
'lang': fields.selection(_get_languages, 'Language', required=True),
|
||||
'format': fields.selection([('csv','CSV File'),
|
||||
('po','PO File'),
|
||||
('tgz', 'TGZ Archive')], 'File Format', required=True),
|
||||
'modules': fields.many2many('ir.module.module', 'rel_modules_langexport', 'wiz_id', 'module_id', 'Modules To Export', domain=[('state','=','installed')]),
|
||||
'data': fields.binary('File', readonly=True),
|
||||
'state': fields.selection([('choose', 'choose'), # choose language
|
||||
('get', 'get')]) # get the file
|
||||
}
|
||||
_defaults = {
|
||||
'state': 'choose',
|
||||
'name': 'lang.tar.gz',
|
||||
'lang': NEW_LANG_KEY,
|
||||
'format': 'csv',
|
||||
}
|
||||
|
||||
def act_getfile(self, cr, uid, ids, context=None):
|
||||
this = self.browse(cr, uid, ids)[0]
|
||||
lang = this.lang if this.lang != NEW_LANG_KEY else False
|
||||
mods = map(lambda m: m.name, this.modules) or ['all']
|
||||
mods.sort()
|
||||
buf=cStringIO.StringIO()
|
||||
tools.trans_export(this.lang, mods, buf, this.format, cr)
|
||||
if this.format == 'csv':
|
||||
this.advice = _("Save this document to a .CSV file and open it with your favourite spreadsheet software. The file encoding is UTF-8. You have to translate the latest column before reimporting it.")
|
||||
elif this.format == 'po':
|
||||
if not this.lang:
|
||||
this.format = 'pot'
|
||||
this.advice = _("Save this document to a %s file and edit it with a specific software or a text editor. The file encoding is UTF-8.") % ('.'+this.format,)
|
||||
elif this.format == 'tgz':
|
||||
ext = this.lang and '.po' or '.pot'
|
||||
this.advice = _('Save this document to a .tgz file. This archive containt UTF-8 %s files and may be uploaded to launchpad.') % (ext,)
|
||||
filename = _('new')
|
||||
if not this.lang and len(mods) == 1:
|
||||
buf = cStringIO.StringIO()
|
||||
tools.trans_export(lang, mods, buf, this.format, cr)
|
||||
filename = 'new'
|
||||
if lang:
|
||||
filename = get_iso_codes(lang)
|
||||
elif len(mods) == 1:
|
||||
filename = mods[0]
|
||||
if this.lang:
|
||||
filename = get_iso_codes(this.lang)
|
||||
this.name = "%s.%s" % (filename, this.format)
|
||||
out=base64.encodestring(buf.getvalue())
|
||||
out = base64.encodestring(buf.getvalue())
|
||||
buf.close()
|
||||
return self.write(cr, uid, ids, {'state':'get', 'data':out, 'advice':this.advice, 'name':this.name}, context=context)
|
||||
self.write(cr, uid, ids, {'state': 'get',
|
||||
'data': out,
|
||||
'name':this.name}, context=context)
|
||||
return True
|
||||
|
||||
_name = "base.language.export"
|
||||
_inherit = "ir.wizard.screen"
|
||||
_columns = {
|
||||
'name': fields.char('File Name', 16, readonly=True),
|
||||
'lang': fields.selection(_get_languages, 'Language', help='To export a new language, do not select a language.'), # not required: unset = new language
|
||||
'format': fields.selection( ( ('csv','CSV File'), ('po','PO File'), ('tgz', 'TGZ Archive')), 'File Format', required=True),
|
||||
'modules': fields.many2many('ir.module.module', 'rel_modules_langexport', 'wiz_id', 'module_id', 'Modules', domain=[('state','=','installed')]),
|
||||
'data': fields.binary('File', readonly=True),
|
||||
'advice': fields.text('Advice', readonly=True),
|
||||
'state': fields.selection( ( ('choose','choose'), # choose language
|
||||
('get','get'), # get the file
|
||||
) ),
|
||||
}
|
||||
_defaults = {
|
||||
'state': lambda *a: 'choose',
|
||||
'name': lambda *a: 'lang.tar.gz'
|
||||
}
|
||||
base_language_export()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -1,31 +1,43 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="wizard_lang_export" model="ir.ui.view">
|
||||
<field name="name">Export Translations</field>
|
||||
<field name="model">base.language.export</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Export Translations" version="7.0">
|
||||
<group colspan="4" states="choose">
|
||||
<separator colspan="4" string="Export Translation"/>
|
||||
<field invisible="1" name="state"/>
|
||||
<field name="name" invisible="1"/>
|
||||
<group states="choose" string="Export Settings">
|
||||
<field name="lang"/>
|
||||
<field name="format" required="1"/>
|
||||
<field name="modules" nolabel="1"/>
|
||||
<field invisible="1" name="state"/>
|
||||
</group>
|
||||
<group colspan="4" states="get">
|
||||
<separator string="Export done" colspan="4"/>
|
||||
<field name="name" invisible="1" colspan="4"/>
|
||||
<field name="data" nolabel="1" readonly="1" filename="name" colspan="4"/>
|
||||
<field height="80" name="advice" nolabel="1" colspan="4"/>
|
||||
<field name="format"/>
|
||||
<field name="modules"/>
|
||||
</group>
|
||||
<div states="get">
|
||||
<h2>Export Complete</h2>
|
||||
<p>Here is the exported translation file: <field name="data" readonly="1" filename="name"/></p>
|
||||
<p>This file was generated using the universal <strong>Unicode/UTF-8</strong> file encoding, please be sure to view and edit
|
||||
using the same encoding.</p>
|
||||
<p>The next step depends on the file format:
|
||||
<ul>
|
||||
<li>CSV format: you may edit it directly with your favorite spreadsheet software,
|
||||
the rightmost column (value) contains the translations</li>
|
||||
<li>PO(T) format: you should edit it with a PO editor such as
|
||||
<a href="http://www.poedit.net/" target="_blank">POEdit</a>, or your preferred text editor</li>
|
||||
<li>TGZ format: this is a compressed archive containing a PO file, directly suitable
|
||||
for uploading to OpenERP's translation platform,
|
||||
<a href="https://translations.launchpad.net/openobject-addons" target="_blank">Launchpad</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
<p>For more details about translating OpenERP in your language, please refer to the
|
||||
<a href="http://doc.openerp.com/v6.1/contribute/07_improving_translations.html" target="_blank">documentation</a>.</p>
|
||||
</div>
|
||||
<footer states="choose">
|
||||
<button name="act_getfile" string="_Export" type="object" class="oe_highlight"/> or
|
||||
<button name="act_cancel" special="cancel" string="_Cancel" type="object" class="oe_link"/>
|
||||
<button name="act_getfile" string="Export" type="object" class="oe_highlight"/> or
|
||||
<button special="cancel" string="Cancel" type="object" class="oe_link"/>
|
||||
</footer>
|
||||
<footer states="get">
|
||||
<button name="act_cancel" special="cancel" string="_Close" type="object"/>
|
||||
<button special="cancel" string="Close" type="object"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -29,11 +29,9 @@ class base_language_import(osv.osv_memory):
|
|||
|
||||
_name = "base.language.import"
|
||||
_description = "Language Import"
|
||||
_inherit = "ir.wizard.screen"
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Language Name',size=64 , required=True),
|
||||
'code': fields.char('Code (eg:en__US)',size=5 , required=True),
|
||||
'name': fields.char('Language Name', size=64 , required=True),
|
||||
'code': fields.char('ISO Code', size=5, help="ISO Language and Country code, e.g. en_US", required=True),
|
||||
'data': fields.binary('File', required=True),
|
||||
'overwrite': fields.boolean('Overwrite Existing Terms',
|
||||
help="If you enable this option, existing translations (including custom ones) "
|
||||
|
@ -41,31 +39,25 @@ class base_language_import(osv.osv_memory):
|
|||
}
|
||||
|
||||
def import_lang(self, cr, uid, ids, context=None):
|
||||
"""
|
||||
Import Language
|
||||
@param cr: the current row, from the database cursor.
|
||||
@param uid: the current user’s ID for security checks.
|
||||
@param ids: the ID or list of IDs
|
||||
@param context: A standard dictionary
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
import_data = self.browse(cr, uid, ids)[0]
|
||||
if import_data.overwrite:
|
||||
this = self.browse(cr, uid, ids[0])
|
||||
if this.overwrite:
|
||||
context.update(overwrite=True)
|
||||
fileobj = TemporaryFile('w+')
|
||||
fileobj.write(base64.decodestring(import_data.data))
|
||||
try:
|
||||
fileobj.write(base64.decodestring(this.data))
|
||||
|
||||
# now we determine the file format
|
||||
fileobj.seek(0)
|
||||
first_line = fileobj.readline().strip().replace('"', '').replace(' ', '')
|
||||
fileformat = first_line.endswith("type,name,res_id,src,value") and 'csv' or 'po'
|
||||
fileobj.seek(0)
|
||||
|
||||
tools.trans_load_data(cr, fileobj, fileformat, this.code, lang_name=this.name, context=context)
|
||||
finally:
|
||||
fileobj.close()
|
||||
return True
|
||||
|
||||
# now we determine the file format
|
||||
fileobj.seek(0)
|
||||
first_line = fileobj.readline().strip().replace('"', '').replace(' ', '')
|
||||
fileformat = first_line.endswith("type,name,res_id,src,value") and 'csv' or 'po'
|
||||
fileobj.seek(0)
|
||||
|
||||
tools.trans_load_data(cr, fileobj, fileformat, import_data.code, lang_name=import_data.name, context=context)
|
||||
fileobj.close()
|
||||
return {}
|
||||
|
||||
base_language_import()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<field name="arch" type="xml">
|
||||
<form string="Import Translation" version="7.0">
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="name" placeholder="e.g. English"/>
|
||||
<field name="code" string="Code" placeholder="e.g. en_US"/>
|
||||
<field name="data"/>
|
||||
<field name="overwrite"/>
|
||||
|
|
|
@ -27,9 +27,7 @@ class base_language_install(osv.osv_memory):
|
|||
""" Install Language"""
|
||||
|
||||
_name = "base.language.install"
|
||||
_inherit = "ir.wizard.screen"
|
||||
_description = "Install Language"
|
||||
|
||||
_columns = {
|
||||
'lang': fields.selection(tools.scan_languages(),'Language', required=True),
|
||||
'overwrite': fields.boolean('Overwrite Existing Terms', help="If you check this box, your customized translations will be overwritten and replaced by the official ones."),
|
||||
|
@ -63,6 +61,5 @@ class base_language_install(osv.osv_memory):
|
|||
'target': 'new',
|
||||
'res_id': ids and ids[0] or False,
|
||||
}
|
||||
base_language_install()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -34,9 +34,7 @@ class base_module_import(osv.osv_memory):
|
|||
""" Import Module """
|
||||
|
||||
_name = "base.module.import"
|
||||
_inherit = "ir.wizard.screen"
|
||||
_description = "Import Module"
|
||||
|
||||
_columns = {
|
||||
'module_file': fields.binary('Module .ZIP file', required=True),
|
||||
'state':fields.selection([('init','init'),('done','done')],
|
||||
|
@ -88,7 +86,5 @@ class base_module_import(osv.osv_memory):
|
|||
'type': 'ir.actions.act_window',
|
||||
}
|
||||
|
||||
base_module_import()
|
||||
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -25,7 +25,6 @@ class base_module_update(osv.osv_memory):
|
|||
|
||||
_name = "base.module.update"
|
||||
_description = "Update Module"
|
||||
_inherit = "ir.wizard.screen"
|
||||
|
||||
_columns = {
|
||||
'update': fields.integer('Number of modules updated', readonly=True),
|
||||
|
@ -55,7 +54,4 @@ class base_module_update(osv.osv_memory):
|
|||
}
|
||||
return res
|
||||
|
||||
|
||||
base_module_update()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -21,31 +21,28 @@
|
|||
|
||||
from osv import osv, fields
|
||||
import tools
|
||||
import pooler
|
||||
import cStringIO
|
||||
from tools.translate import _
|
||||
|
||||
class base_update_translations(osv.osv_memory):
|
||||
def _get_languages(self, cr, uid, context):
|
||||
lang_obj=pooler.get_pool(cr.dbname).get('res.lang')
|
||||
ids=lang_obj.search(cr, uid, ['&', ('active', '=', True), ('translatable', '=', True),])
|
||||
langs=lang_obj.browse(cr, uid, ids)
|
||||
lang_obj = self.pool.get('res.lang')
|
||||
ids = lang_obj.search(cr, uid, ['&', ('active', '=', True), ('translatable', '=', True),])
|
||||
langs = lang_obj.browse(cr, uid, ids)
|
||||
return [(lang.code, lang.name) for lang in langs]
|
||||
|
||||
def _get_lang_name(self, cr, uid, lang_code):
|
||||
lang_obj=pooler.get_pool(cr.dbname).get('res.lang')
|
||||
ids=lang_obj.search(cr, uid, [('code', '=', lang_code)])
|
||||
lang_obj = self.pool.get('res.lang')
|
||||
ids = lang_obj.search(cr, uid, [('code', '=', lang_code)])
|
||||
if not ids:
|
||||
raise osv.except_osv(_('Error!'), _('No language with code "%s" exists') % lang_code)
|
||||
lang = lang_obj.browse(cr, uid, ids[0])
|
||||
return lang.name
|
||||
def act_cancel(self, cr, uid, ids, context=None):
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def act_update(self, cr, uid, ids, context=None):
|
||||
this = self.browse(cr, uid, ids)[0]
|
||||
lang_name = self._get_lang_name(cr, uid, this.lang)
|
||||
buf=cStringIO.StringIO()
|
||||
buf = cStringIO.StringIO()
|
||||
tools.trans_export(this.lang, ['all'], buf, 'csv', cr)
|
||||
tools.trans_load_data(cr, buf, 'csv', this.lang, lang_name=lang_name)
|
||||
buf.close()
|
||||
|
@ -66,11 +63,8 @@ class base_update_translations(osv.osv_memory):
|
|||
return res
|
||||
|
||||
_name = 'base.update.translations'
|
||||
_inherit = "ir.wizard.screen"
|
||||
_columns = {
|
||||
'lang': fields.selection(_get_languages, 'Language', required=True),
|
||||
}
|
||||
|
||||
base_update_translations()
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<footer>
|
||||
<button name="act_update" string="Update" type="object" class="oe_highlight"/>
|
||||
or
|
||||
<button name="act_cancel" special="cancel" string="Cancel" type="object" class="oe_link"/>
|
||||
<button special="cancel" string="Cancel" type="object" class="oe_link"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
|
|
|
@ -37,7 +37,6 @@ class res_config_configurable(osv.osv_memory):
|
|||
their view inherit from the related res_config_view_base view.
|
||||
'''
|
||||
_name = 'res.config'
|
||||
_inherit = 'ir.wizard.screen'
|
||||
|
||||
def _next_action(self, cr, uid, context=None):
|
||||
Todos = self.pool['ir.actions.todo']
|
||||
|
|
|
@ -33,6 +33,7 @@ import logging
|
|||
import tarfile
|
||||
import tempfile
|
||||
import threading
|
||||
from babel.messages import extract
|
||||
from os.path import join
|
||||
|
||||
from datetime import datetime
|
||||
|
@ -47,6 +48,9 @@ from openerp import SUPERUSER_ID
|
|||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
# used to notify web client that these translations should be loaded in the UI
|
||||
WEB_TRANSLATION_COMMENT = "openerp-web"
|
||||
|
||||
_LOCALE2WIN32 = {
|
||||
'af_ZA': 'Afrikaans_South Africa',
|
||||
'sq_AL': 'Albanian_Albania',
|
||||
|
@ -262,7 +266,7 @@ class TinyPoFile(object):
|
|||
self.lines_count = len(self.lines);
|
||||
|
||||
self.first = True
|
||||
self.tnrs= []
|
||||
self.extra_lines= []
|
||||
return self
|
||||
|
||||
def _get_lines(self):
|
||||
|
@ -278,14 +282,14 @@ class TinyPoFile(object):
|
|||
return (self.lines_count - len(self.lines))
|
||||
|
||||
def next(self):
|
||||
type = name = res_id = source = trad = None
|
||||
|
||||
if self.tnrs:
|
||||
type, name, res_id, source, trad = self.tnrs.pop(0)
|
||||
trans_type = name = res_id = source = trad = None
|
||||
if self.extra_lines:
|
||||
trans_type, name, res_id, source, trad, comments = self.extra_lines.pop(0)
|
||||
if not res_id:
|
||||
res_id = '0'
|
||||
else:
|
||||
tmp_tnrs = []
|
||||
comments = []
|
||||
targets = []
|
||||
line = None
|
||||
fuzzy = False
|
||||
while (not line):
|
||||
|
@ -295,15 +299,20 @@ class TinyPoFile(object):
|
|||
while line.startswith('#'):
|
||||
if line.startswith('#~ '):
|
||||
break
|
||||
if line.startswith('#:'):
|
||||
if line.startswith('#.'):
|
||||
line = line[2:].strip()
|
||||
if not line.startswith('module:'):
|
||||
comments.append(line)
|
||||
elif line.startswith('#:'):
|
||||
for lpart in line[2:].strip().split(' '):
|
||||
trans_info = lpart.strip().split(':',2)
|
||||
if trans_info and len(trans_info) == 2:
|
||||
# looks like the translation type is missing, which is not
|
||||
# looks like the translation trans_type is missing, which is not
|
||||
# unexpected because it is not a GetText standard. Default: 'code'
|
||||
trans_info[:0] = ['code']
|
||||
if trans_info and len(trans_info) == 3:
|
||||
tmp_tnrs.append(trans_info)
|
||||
# this is a ref line holding the destination info (model, field, record)
|
||||
targets.append(trans_info)
|
||||
elif line.startswith('#,') and (line[2:].strip() == 'fuzzy'):
|
||||
fuzzy = True
|
||||
line = self.lines.pop(0).strip()
|
||||
|
@ -326,7 +335,7 @@ class TinyPoFile(object):
|
|||
# if the source is "" and it's the first msgid, it's the special
|
||||
# msgstr with the informations about the traduction and the
|
||||
# traductor; we skip it
|
||||
self.tnrs = []
|
||||
self.extra_lines = []
|
||||
while line:
|
||||
line = self.lines.pop(0).strip()
|
||||
return self.next()
|
||||
|
@ -343,10 +352,11 @@ class TinyPoFile(object):
|
|||
trad += unquote(line)
|
||||
line = self.lines.pop(0).strip()
|
||||
|
||||
if tmp_tnrs and not fuzzy:
|
||||
type, name, res_id = tmp_tnrs.pop(0)
|
||||
for t, n, r in tmp_tnrs:
|
||||
self.tnrs.append((t, n, r, source, trad))
|
||||
if targets and not fuzzy:
|
||||
trans_type, name, res_id = targets.pop(0)
|
||||
for t, n, r in targets:
|
||||
if t == trans_type == 'code': continue
|
||||
self.extra_lines.append((t, n, r, source, trad, comments))
|
||||
|
||||
self.first = False
|
||||
|
||||
|
@ -355,7 +365,7 @@ class TinyPoFile(object):
|
|||
self.warn('Missing "#:" formated comment at line %d for the following source:\n\t%s',
|
||||
self.cur_line(), source[:30])
|
||||
return self.next()
|
||||
return type, name, res_id, source, trad
|
||||
return trans_type, name, res_id, source, trad, '\n'.join(comments)
|
||||
|
||||
def write_infos(self, modules):
|
||||
import openerp.release as release
|
||||
|
@ -384,11 +394,13 @@ class TinyPoFile(object):
|
|||
}
|
||||
)
|
||||
|
||||
def write(self, modules, tnrs, source, trad):
|
||||
def write(self, modules, tnrs, source, trad, comments=None):
|
||||
|
||||
plurial = len(modules) > 1 and 's' or ''
|
||||
self.buffer.write("#. module%s: %s\n" % (plurial, ', '.join(modules)))
|
||||
|
||||
if comments:
|
||||
self.buffer.write(''.join(('#. %s\n' % c for c in comments)))
|
||||
|
||||
code = False
|
||||
for typy, name, res_id in tnrs:
|
||||
|
@ -415,44 +427,43 @@ class TinyPoFile(object):
|
|||
|
||||
def trans_export(lang, modules, buffer, format, cr):
|
||||
|
||||
def _process(format, modules, rows, buffer, lang, newlang):
|
||||
def _process(format, modules, rows, buffer, lang):
|
||||
if format == 'csv':
|
||||
writer=csv.writer(buffer, 'UNIX')
|
||||
for row in rows:
|
||||
writer.writerow(row)
|
||||
writer = csv.writer(buffer, 'UNIX')
|
||||
# write header first
|
||||
writer.writerow(("module","type","name","res_id","src","value"))
|
||||
for module, type, name, res_id, src, trad, comments in rows:
|
||||
# Comments are ignored by the CSV writer
|
||||
writer.writerow((module, type, name, res_id, src, trad))
|
||||
elif format == 'po':
|
||||
rows.pop(0)
|
||||
writer = TinyPoFile(buffer)
|
||||
writer.write_infos(modules)
|
||||
|
||||
# we now group the translations by source. That means one translation per source.
|
||||
grouped_rows = {}
|
||||
for module, type, name, res_id, src, trad in rows:
|
||||
for module, type, name, res_id, src, trad, comments in rows:
|
||||
row = grouped_rows.setdefault(src, {})
|
||||
row.setdefault('modules', set()).add(module)
|
||||
if ('translation' not in row) or (not row['translation']):
|
||||
row['translation'] = trad
|
||||
row.setdefault('tnrs', []).append((type, name, res_id))
|
||||
row.setdefault('comments', set()).update(comments)
|
||||
|
||||
for src, row in grouped_rows.items():
|
||||
writer.write(row['modules'], row['tnrs'], src, row['translation'])
|
||||
writer.write(row['modules'], row['tnrs'], src, row['translation'], row['comments'])
|
||||
|
||||
elif format == 'tgz':
|
||||
rows.pop(0)
|
||||
rows_by_module = {}
|
||||
for row in rows:
|
||||
module = row[0]
|
||||
# first row is the "header", as in csv, it will be popped
|
||||
rows_by_module.setdefault(module, [['module', 'type', 'name', 'res_id', 'src', ''],])
|
||||
rows_by_module[module].append(row)
|
||||
|
||||
rows_by_module.setdefault(module, []).append(row)
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
for mod, modrows in rows_by_module.items():
|
||||
tmpmoddir = join(tmpdir, mod, 'i18n')
|
||||
os.makedirs(tmpmoddir)
|
||||
pofilename = (newlang and mod or lang) + ".po" + (newlang and 't' or '')
|
||||
pofilename = (lang if lang else mod) + ".po" + ('t' if not lang else '')
|
||||
buf = file(join(tmpmoddir, pofilename), 'w')
|
||||
_process('po', [mod], modrows, buf, lang, newlang)
|
||||
_process('po', [mod], modrows, buf, lang)
|
||||
buf.close()
|
||||
|
||||
tar = tarfile.open(fileobj=buffer, mode='w|gz')
|
||||
|
@ -463,16 +474,15 @@ def trans_export(lang, modules, buffer, format, cr):
|
|||
raise Exception(_('Unrecognized extension: must be one of '
|
||||
'.csv, .po, or .tgz (received .%s).' % format))
|
||||
|
||||
newlang = not bool(lang)
|
||||
if newlang:
|
||||
lang = 'en_US'
|
||||
trans = trans_generate(lang, modules, cr)
|
||||
if newlang and format!='csv':
|
||||
for trx in trans:
|
||||
trx[-1] = ''
|
||||
modules = set([t[0] for t in trans[1:]])
|
||||
_process(format, modules, trans, buffer, lang, newlang)
|
||||
del trans
|
||||
trans_lang = lang
|
||||
if not trans_lang and format == 'csv':
|
||||
# CSV files are meant for translators and they need a starting point,
|
||||
# so we at least put the original term in the translation column
|
||||
trans_lang = 'en_US'
|
||||
translations = trans_generate(lang, modules, cr)
|
||||
modules = set([t[0] for t in translations[1:]])
|
||||
_process(format, modules, translations, buffer, lang)
|
||||
del translations
|
||||
|
||||
def trans_parse_xsl(de):
|
||||
res = []
|
||||
|
@ -535,6 +545,46 @@ def in_modules(object_name, modules):
|
|||
module = module_dict.get(module, module)
|
||||
return module in modules
|
||||
|
||||
|
||||
def babel_extract_qweb(fileobj, keywords, comment_tags, options):
|
||||
"""Babel message extractor for qweb template files.
|
||||
:param fileobj: the file-like object the messages should be extracted from
|
||||
:param keywords: a list of keywords (i.e. function names) that should
|
||||
be recognized as translation functions
|
||||
:param comment_tags: a list of translator tags to search for and
|
||||
include in the results
|
||||
:param options: a dictionary of additional options (optional)
|
||||
:return: an iterator over ``(lineno, funcname, message, comments)``
|
||||
tuples
|
||||
:rtype: ``iterator``
|
||||
"""
|
||||
result = []
|
||||
def handle_text(text, lineno):
|
||||
text = (text or "").strip()
|
||||
if len(text) > 1: # Avoid mono-char tokens like ':' ',' etc.
|
||||
result.append((lineno, None, text, []))
|
||||
|
||||
# not using elementTree.iterparse because we need to skip sub-trees in case
|
||||
# the ancestor element had a reason to be skipped
|
||||
def iter_elements(current_element):
|
||||
for el in current_element:
|
||||
if isinstance(el, SKIPPED_ELEMENT_TYPES): continue
|
||||
if "t-js" not in el.attrib and \
|
||||
not ("t-jquery" in el.attrib and "t-operation" not in el.attrib) and \
|
||||
not ("t-translation" in el.attrib and el.attrib["t-translation"].strip() == "off"):
|
||||
handle_text(el.text, el.sourceline)
|
||||
for att in ('title', 'alt', 'label', 'placeholder'):
|
||||
if att in el.attrib:
|
||||
handle_text(el.attrib[att], el.sourceline)
|
||||
iter_elements(el)
|
||||
handle_text(el.tail, el.sourceline)
|
||||
|
||||
tree = etree.parse(fileobj)
|
||||
iter_elements(tree.getroot())
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def trans_generate(lang, modules, cr):
|
||||
dbname = cr.dbname
|
||||
|
||||
|
@ -566,8 +616,8 @@ def trans_generate(lang, modules, cr):
|
|||
cr.execute(query, query_param)
|
||||
|
||||
_to_translate = []
|
||||
def push_translation(module, type, name, id, source):
|
||||
tuple = (module, source, name, id, type)
|
||||
def push_translation(module, type, name, id, source, comments=None):
|
||||
tuple = (module, source, name, id, type, comments or [])
|
||||
if source and tuple not in _to_translate:
|
||||
_to_translate.append(tuple)
|
||||
|
||||
|
@ -717,7 +767,7 @@ def trans_generate(lang, modules, cr):
|
|||
if not hasattr(msg, '__call__'):
|
||||
push_translation(module, term_type, model, 0, encode(msg))
|
||||
|
||||
for (model_id, model, module) in cr.fetchall():
|
||||
for (_, model, module) in cr.fetchall():
|
||||
module = encode(module)
|
||||
model = encode(model)
|
||||
|
||||
|
@ -733,7 +783,6 @@ def trans_generate(lang, modules, cr):
|
|||
for constraint in getattr(model_obj, '_sql_constraints', []):
|
||||
push_constraint_msg(module, 'sql_constraint', model, constraint[2])
|
||||
|
||||
# parse source code for _() calls
|
||||
def get_module_from_path(path, mod_paths=None):
|
||||
if not mod_paths:
|
||||
# First, construct a list of possible paths
|
||||
|
@ -771,92 +820,71 @@ def trans_generate(lang, modules, cr):
|
|||
_logger.debug("Scanning modules at paths: ", path_list)
|
||||
|
||||
mod_paths = []
|
||||
join_dquotes = re.compile(r'([^\\])"[\s\\]*"', re.DOTALL)
|
||||
join_quotes = re.compile(r'([^\\])\'[\s\\]*\'', re.DOTALL)
|
||||
re_dquotes = re.compile(r'[^a-zA-Z0-9_]_\([\s]*"(.+?)"[\s]*?\)', re.DOTALL)
|
||||
re_quotes = re.compile(r'[^a-zA-Z0-9_]_\([\s]*\'(.+?)\'[\s]*?\)', re.DOTALL)
|
||||
|
||||
def export_code_terms_from_file(fname, path, root, terms_type):
|
||||
def verified_module_filepaths(fname, path, root):
|
||||
fabsolutepath = join(root, fname)
|
||||
frelativepath = fabsolutepath[len(path):]
|
||||
display_path = "addons%s" % frelativepath
|
||||
module = get_module_from_path(fabsolutepath, mod_paths=mod_paths)
|
||||
is_mod_installed = module in installed_modules
|
||||
if (('all' in modules) or (module in modules)) and is_mod_installed:
|
||||
_logger.debug("Scanning code of %s at module: %s", frelativepath, module)
|
||||
src_file = misc.file_open(fabsolutepath, subdir='')
|
||||
if (('all' in modules) or (module in modules)) and module in installed_modules:
|
||||
return module, fabsolutepath, frelativepath, display_path
|
||||
return None, None, None, None
|
||||
|
||||
def babel_extract_terms(fname, path, root, extract_method="python", trans_type='code',
|
||||
extra_comments=None, extract_keywords={'_': None}):
|
||||
module, fabsolutepath, _, display_path = verified_module_filepaths(fname, path, root)
|
||||
extra_comments = extra_comments or []
|
||||
if module:
|
||||
src_file = open(fabsolutepath, 'r')
|
||||
try:
|
||||
code_string = src_file.read()
|
||||
for lineno, message, comments in extract.extract(extract_method, src_file,
|
||||
keywords=extract_keywords):
|
||||
push_translation(module, trans_type, display_path, lineno,
|
||||
encode(message), comments + extra_comments)
|
||||
finally:
|
||||
src_file.close()
|
||||
if module in installed_modules:
|
||||
frelativepath = str("addons" + frelativepath)
|
||||
ite = re_dquotes.finditer(code_string)
|
||||
code_offset = 0
|
||||
code_line = 1
|
||||
for i in ite:
|
||||
src = i.group(1)
|
||||
if src.startswith('""'):
|
||||
assert src.endswith('""'), "Incorrect usage of _(..) function (should contain only literal strings!) in file %s near: %s" % (frelativepath, src[:30])
|
||||
src = src[2:-2]
|
||||
else:
|
||||
src = join_dquotes.sub(r'\1', src)
|
||||
# try to count the lines from the last pos to our place:
|
||||
code_line += code_string[code_offset:i.start(1)].count('\n')
|
||||
# now, since we did a binary read of a python source file, we
|
||||
# have to expand pythonic escapes like the interpreter does.
|
||||
src = src.decode('string_escape')
|
||||
push_translation(module, terms_type, frelativepath, code_line, encode(src))
|
||||
code_line += i.group(1).count('\n')
|
||||
code_offset = i.end() # we have counted newlines up to the match end
|
||||
|
||||
ite = re_quotes.finditer(code_string)
|
||||
code_offset = 0 #reset counters
|
||||
code_line = 1
|
||||
for i in ite:
|
||||
src = i.group(1)
|
||||
if src.startswith("''"):
|
||||
assert src.endswith("''"), "Incorrect usage of _(..) function (should contain only literal strings!) in file %s near: %s" % (frelativepath, src[:30])
|
||||
src = src[2:-2]
|
||||
else:
|
||||
src = join_quotes.sub(r'\1', src)
|
||||
code_line += code_string[code_offset:i.start(1)].count('\n')
|
||||
src = src.decode('string_escape')
|
||||
push_translation(module, terms_type, frelativepath, code_line, encode(src))
|
||||
code_line += i.group(1).count('\n')
|
||||
code_offset = i.end() # we have counted newlines up to the match end
|
||||
|
||||
for path in path_list:
|
||||
_logger.debug("Scanning files of modules at %s", path)
|
||||
for root, dummy, files in osutil.walksymlinks(path):
|
||||
for fname in itertools.chain(fnmatch.filter(files, '*.py')):
|
||||
export_code_terms_from_file(fname, path, root, 'code')
|
||||
for fname in itertools.chain(fnmatch.filter(files, '*.mako')):
|
||||
export_code_terms_from_file(fname, path, root, 'report')
|
||||
for fname in fnmatch.filter(files, '*.py'):
|
||||
babel_extract_terms(fname, path, root)
|
||||
for fname in fnmatch.filter(files, '*.mako'):
|
||||
babel_extract_terms(fname, path, root, trans_type='report')
|
||||
# Javascript source files in the static/src/js directory, rest is ignored (libs)
|
||||
if fnmatch.fnmatch(root, '*/static/src/js*'):
|
||||
for fname in fnmatch.filter(files, '*.js'):
|
||||
babel_extract_terms(fname, path, root, 'javascript',
|
||||
extra_comments=[WEB_TRANSLATION_COMMENT],
|
||||
extract_keywords={'_t': None})
|
||||
# QWeb template files
|
||||
if fnmatch.fnmatch(root, '*/static/src/xml*'):
|
||||
for fname in fnmatch.filter(files, '*.xml'):
|
||||
babel_extract_terms(fname, path, root, 'openerp.tools.translate:babel_extract_qweb',
|
||||
extra_comments=[WEB_TRANSLATION_COMMENT])
|
||||
|
||||
|
||||
out = [["module","type","name","res_id","src","value"]] # header
|
||||
out = []
|
||||
_to_translate.sort()
|
||||
# translate strings marked as to be translated
|
||||
for module, source, name, id, type in _to_translate:
|
||||
trans = trans_obj._get_source(cr, uid, name, type, lang, source)
|
||||
out.append([module, type, name, id, source, encode(trans) or ''])
|
||||
|
||||
for module, source, name, id, type, comments in _to_translate:
|
||||
trans = '' if not lang else trans_obj._get_source(cr, uid, name, type, lang, source)
|
||||
out.append([module, type, name, id, source, encode(trans) or '', comments])
|
||||
return out
|
||||
|
||||
def trans_load(cr, filename, lang, verbose=True, context=None):
|
||||
def trans_load(cr, filename, lang, verbose=True, module_name=None, context=None):
|
||||
try:
|
||||
fileobj = misc.file_open(filename)
|
||||
_logger.info("loading %s", filename)
|
||||
fileformat = os.path.splitext(filename)[-1][1:].lower()
|
||||
r = trans_load_data(cr, fileobj, fileformat, lang, verbose=verbose, context=context)
|
||||
result = trans_load_data(cr, fileobj, fileformat, lang, verbose=verbose, module_name=module_name, context=context)
|
||||
fileobj.close()
|
||||
return r
|
||||
return result
|
||||
except IOError:
|
||||
if verbose:
|
||||
_logger.error("couldn't read translation file %s", filename)
|
||||
return None
|
||||
|
||||
def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True, context=None):
|
||||
def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True, module_name=None, context=None):
|
||||
"""Populates the ir_translation table."""
|
||||
if verbose:
|
||||
_logger.info('loading translation file for language %s', lang)
|
||||
|
@ -868,8 +896,7 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
|
|||
trans_obj = pool.get('ir.translation')
|
||||
iso_lang = misc.get_iso_codes(lang)
|
||||
try:
|
||||
uid = 1
|
||||
ids = lang_obj.search(cr, uid, [('code','=', lang)])
|
||||
ids = lang_obj.search(cr, SUPERUSER_ID, [('code','=', lang)])
|
||||
|
||||
if not ids:
|
||||
# lets create the language with locale information
|
||||
|
@ -886,14 +913,14 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
|
|||
break
|
||||
elif fileformat == 'po':
|
||||
reader = TinyPoFile(fileobj)
|
||||
f = ['type', 'name', 'res_id', 'src', 'value']
|
||||
f = ['type', 'name', 'res_id', 'src', 'value', 'comments']
|
||||
else:
|
||||
_logger.error('Bad file format: %s', fileformat)
|
||||
raise Exception(_('Bad file format'))
|
||||
|
||||
# read the rest of the file
|
||||
line = 1
|
||||
irt_cursor = trans_obj._get_import_cursor(cr, uid, context=context)
|
||||
irt_cursor = trans_obj._get_import_cursor(cr, SUPERUSER_ID, context=context)
|
||||
|
||||
for row in reader:
|
||||
line += 1
|
||||
|
@ -903,39 +930,32 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
|
|||
|
||||
# dictionary which holds values for this line of the csv file
|
||||
# {'lang': ..., 'type': ..., 'name': ..., 'res_id': ...,
|
||||
# 'src': ..., 'value': ...}
|
||||
dic = {'lang': lang}
|
||||
dic_module = False
|
||||
for i in range(len(f)):
|
||||
if f[i] in ('module',):
|
||||
continue
|
||||
dic[f[i]] = row[i]
|
||||
# 'src': ..., 'value': ..., 'module':...}
|
||||
dic = dict.fromkeys(('name', 'res_id', 'src', 'type', 'imd_model', 'imd_name', 'module', 'value', 'comments'))
|
||||
dic['lang'] = lang
|
||||
for i, field in enumerate(f):
|
||||
dic[field] = row[i]
|
||||
|
||||
# This would skip terms that fail to specify a res_id
|
||||
if not dic.get('res_id', False):
|
||||
if not dic.get('res_id'):
|
||||
continue
|
||||
|
||||
res_id = dic.pop('res_id')
|
||||
if res_id and isinstance(res_id, (int, long)) \
|
||||
or (isinstance(res_id, basestring) and res_id.isdigit()):
|
||||
dic['res_id'] = int(res_id)
|
||||
dic['module'] = module_name
|
||||
else:
|
||||
try:
|
||||
tmodel = dic['name'].split(',')[0]
|
||||
if '.' in res_id:
|
||||
tmodule, tname = res_id.split('.', 1)
|
||||
else:
|
||||
tmodule = dic_module
|
||||
tname = res_id
|
||||
dic['imd_model'] = tmodel
|
||||
dic['imd_module'] = tmodule
|
||||
dic['imd_name'] = tname
|
||||
|
||||
dic['res_id'] = None
|
||||
except Exception:
|
||||
_logger.warning("Could not decode resource for %s, please fix the po file.",
|
||||
dic['res_id'], exc_info=True)
|
||||
dic['res_id'] = None
|
||||
tmodel = dic['name'].split(',')[0]
|
||||
if '.' in res_id:
|
||||
tmodule, tname = res_id.split('.', 1)
|
||||
else:
|
||||
tmodule = False
|
||||
tname = res_id
|
||||
dic['imd_model'] = tmodel
|
||||
dic['imd_name'] = tname
|
||||
dic['module'] = tmodule
|
||||
dic['res_id'] = None
|
||||
|
||||
irt_cursor.push(dic)
|
||||
|
||||
|
|
Loading…
Reference in New Issue