[FIX] account: fix some translations related to chart template
There are currently translation issues related to the chart template.
Due to this the names of some records do not receive the necessary
/ intended translations when installing a localization or new language.
When installing a new localization / chart some records have the following
problem with the values of their translatabe fields:
They are only installed in the language that was active when the localization / chart was installed.
Thus when switching languages (or using a different user with a different language)
the names are displayed in the "installation language".
Translations for all active languages should be installed (for all relevant records).
The same problem happens when installing a new language
(the same records do not receive a translation for the new language).
The translation issue concerns for example (some) accounts and journals;
see the (incomplete) list at the end of this message.
This commit tries to fix the translation issue for the translatable
fields of all relevant models.
Note!
=====
* The translation mechanism only works for records with xmlid.
If a module creates a record without xmlid it will not be translated.
* The problem is only fixed for records with xmlid for which at least 1 of the
following conditions holds:
* The record (and the translatable field value) is defined in
the body of the function decorated with '@template'
* The translation of the value of the translatable field can
be found in the module 'account' or in the module that
is associated with the record (by 'ir.model.data')
I.e. the problem is not solved for demo data: It is technically
difficult to determine the module they originate from.
This makes it difficult to load the right code translation (the
module information is needed for this).
* The translation mechanism is not necessarily triggered
if the record is (in principal) part of the chart template
but not installed as part of the chart template.
This can i.e. happen if a module is installed after the
localization / chart.
* Example: account.journal "Salaries" from hr_payroll_account
* We also want to "translate" / localize some untranslatable fields
(like account journal codes). For these fields the terms will be
installed in the language of the partner of the company for which
the chart will be installed (fallback to lang / user lang from the
env in case there is none set).
* Currently there is no language set for many (all?) demo comany.
Thus the values will remain in English for them (when the
respective module is installed).
Examples / Details
==================
**Reproduce**:
1. Switch to the French language (install if needed)
(Settings App > General Settings > Languages)
2. Install a localisation (e.g. l10n_fr).
3. Check the French translations of the localization
* Comptabilité > Configuration (Menu) > Journaux
(Accounting > Configuration (Menu) > Journals)
* Here the journal names are in French
* Comptabilité > Configuration (Menu) > Plan comptable
(Accounting > Configuration (Menu) > Chart of Accounts)
* All the account names are in French
4. Switch to English on the current user (or some other language)
via the user profile on the top right.
5. Check the names of the localization again
* Accounting
* The journal names are still in French
* Accounting > Configuration (Menu) > Chart of Accounts
* Some of the account names are still in French
* E.g. "Compte d'attente de la banque" ("Bank Suspense Account")
Other things to test:
* "Salaries" journal from enterprise module 'hr_payroll_account'
* Not demo data; it will (partly) work after this commit (see "Note" above)
* "IFRS Automatic transfers" journal from enterprise module
'account_auto_transfer' (installed when installing l10n_fr)
* Demo data; the problem remains after this commit
**Technically** the main problems are the following:
1. The information of some of the created records is only defined in
the code. Thus their translations have to be taken from the
translation of the code.
But at the point of translation it is not clear from which
module the data came from. This is needed to load the right translation.
* This was fixed for data from '@template' functions
2. Some records are created without an xmlid and thus
cannot be translated with the current translation mechanism at all.
* This was fixed for the relevant records from module 'account'
Example Records
---------------
Some affected **accounts**:
* from module 'account'
* Bank utility accounts
* Bank Suspense Account
* Outstanding Receipts
* Outstanding Payments
* Cash Discount Loss
* Cash Discount Gain
* Cash Difference Loss
* Cash Difference Gain
* Liquidity Transfer
* Bank / Cash journal default accounts
* Bank
* Cash
* Unaffected earnings account
* Undistributed Profits/Losses
Some affected **journals**
* from module 'account'
* Customer Invoices
* Vendor Bills
* Miscellaneuos Operations
* Exchange Difference
* Cash Basis Taxes
* Bank
* Cash
* from module 'account_auto_transfer' (enterprise)
* IFRS Automatic Transfers
* The problem will remain since it is demo data
* from module 'hr_payroll_account' (enterprise)
* Salaries
* The translation is only loaded if the module is installed
before the localization / chart
task info
=========
task-3414329
closes odoo/odoo#158382
X-original-commit: bd6040fb8b1dee9efe0e461a6812ecf7acbb517e
Signed-off-by: William André (wan) <wan@odoo.com>
This commit is contained in:
@@ -668,7 +668,15 @@ class AccountJournal(models.Model):
|
||||
if not has_liquidity_accounts:
|
||||
default_account_code = self.env['account.account']._search_new_account_code(company, digits, liquidity_account_prefix)
|
||||
default_account_vals = self._prepare_liquidity_account_vals(company, default_account_code, vals)
|
||||
vals['default_account_id'] = self.env['account.account'].create(default_account_vals).id
|
||||
default_account = self.env['account.account'].create(default_account_vals)
|
||||
self.env['ir.model.data']._update_xmlids([
|
||||
{
|
||||
'xml_id': f"account.{str(company.id)}_{journal_type}_journal_default_account_{default_account.id}",
|
||||
'record': default_account,
|
||||
'noupdate': True,
|
||||
}
|
||||
])
|
||||
vals['default_account_id'] = default_account.id
|
||||
if journal_type in ('cash', 'bank') and not has_profit_account:
|
||||
vals['profit_account_id'] = company.default_cash_difference_income_account_id.id
|
||||
if journal_type in ('cash', 'bank') and not has_loss_account:
|
||||
|
||||
@@ -16,8 +16,9 @@ from odoo import Command, _, models, api
|
||||
from odoo.addons.base.models.ir_model import MODULE_UNINSTALL_FLAG
|
||||
from odoo.addons.account import SYSCOHADA_LIST
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.tools import file_open, groupby
|
||||
from odoo.tools.translate import TranslationImporter
|
||||
from odoo.modules import get_resource_from_path
|
||||
from odoo.tools import file_open, get_lang, groupby, SQL
|
||||
from odoo.tools.translate import code_translations, TranslationImporter
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -49,7 +50,15 @@ def template(template=None, model='template_data'):
|
||||
# remove the template code argument as we already know it from the decorator
|
||||
args, kwargs = args[:1], {}
|
||||
return func(*args, **kwargs)
|
||||
return api.attrsetter('_l10n_template', (template, model))(wrapper)
|
||||
|
||||
# the module the function originates from is used for code translations
|
||||
path = func.__globals__['__file__']
|
||||
path_info = get_resource_from_path(path)
|
||||
module = path_info[0] if path_info else 'account'
|
||||
|
||||
wrapper._module = module
|
||||
wrapper._l10n_template = (template, model)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
@@ -162,11 +171,16 @@ class AccountChartTemplate(models.AbstractModel):
|
||||
self.env.reset() # clear the envs with an old registry
|
||||
self = self.env()['account.chart.template'] # create a new env with the new registry
|
||||
|
||||
# To be able to use code translation we load everything in 'en_US'
|
||||
# The demo data is still loaded "normally" since code translations cannot be used for them reliably.
|
||||
# (Since we rely on the "@template functions" to determine the module to take the code translations from.)
|
||||
original_context_lang = self.env.context.get('lang')
|
||||
self = self.with_context(
|
||||
default_company_id=company.id,
|
||||
allowed_company_ids=[company.id],
|
||||
tracking_disable=True,
|
||||
delay_account_group_sync=True,
|
||||
lang='en_US',
|
||||
)
|
||||
company = company.with_env(self.env)
|
||||
|
||||
@@ -190,8 +204,8 @@ class AccountChartTemplate(models.AbstractModel):
|
||||
install_demo = False
|
||||
data = self._pre_load_data(template_code, company, template_data, data)
|
||||
self._load_data(data)
|
||||
self._load_translations(companies=company)
|
||||
self._post_load_data(template_code, company, template_data)
|
||||
self._load_translations(companies=company)
|
||||
|
||||
# Manual sync because disable above (delay_account_group_sync)
|
||||
AccountGroup = self.env['account.group'].with_context(delay_account_group_sync=False)
|
||||
@@ -202,6 +216,8 @@ class AccountChartTemplate(models.AbstractModel):
|
||||
if install_demo and self.ref('base.module_account').demo and not reload_template:
|
||||
try:
|
||||
with self.env.cr.savepoint():
|
||||
self = self.with_context(lang=original_context_lang)
|
||||
company = company.with_env(self.env)
|
||||
self.sudo()._load_data(self._get_demo_data(company))
|
||||
self._post_load_demo_data(company)
|
||||
except Exception:
|
||||
@@ -411,6 +427,21 @@ class AccountChartTemplate(models.AbstractModel):
|
||||
if fname not in company._fields:
|
||||
del data['res.company'][company.id][fname]
|
||||
|
||||
# Translate the untranslatable fields we want to translate anyway
|
||||
untranslatable_model_fields = self._get_untranslatable_fields_to_translate()
|
||||
untranslatable_target_lang = self._get_untranslatable_fields_target_language(template_code, company)
|
||||
for model_name, records in data.items():
|
||||
untranslatable_fields = untranslatable_model_fields.get(model_name, [])
|
||||
if not untranslatable_fields:
|
||||
continue
|
||||
for _xmlid, record in records.items():
|
||||
for field in untranslatable_fields:
|
||||
if field not in record:
|
||||
continue
|
||||
translation = self._get_field_translation(record, field, untranslatable_target_lang)
|
||||
if translation:
|
||||
record[field] = translation
|
||||
|
||||
return data
|
||||
|
||||
def _load_data(self, data):
|
||||
@@ -506,7 +537,7 @@ class AccountChartTemplate(models.AbstractModel):
|
||||
for xml_id, record in data.items():
|
||||
# Extract the translations from the values
|
||||
for key in list(record):
|
||||
if '@' in key:
|
||||
if '@' in key or key == '__translation_module__':
|
||||
del record[key]
|
||||
|
||||
# Manage ids given as database id or xml_id
|
||||
@@ -582,11 +613,15 @@ class AccountChartTemplate(models.AbstractModel):
|
||||
def _get_chart_template_data(self, template_code):
|
||||
template_data = defaultdict(lambda: defaultdict(dict))
|
||||
template_data['res.company'] # ensure it's the first property when iterating
|
||||
translatable_model_fields = self._get_translatable_template_model_fields()
|
||||
untranslatable_model_fields = self._get_untranslatable_fields_to_translate()
|
||||
for code in [None] + self._get_parent_template(template_code):
|
||||
for model, funcs in sorted(
|
||||
self._template_register[code].items(),
|
||||
key=lambda i: TEMPLATE_MODELS.index(i[0]) if i[0] in TEMPLATE_MODELS else 1000
|
||||
):
|
||||
translatable_fields = translatable_model_fields.get(model, [])
|
||||
untranslatable_fields = untranslatable_model_fields.get(model, [])
|
||||
for func in funcs:
|
||||
data = func(self, template_code)
|
||||
if data is not None:
|
||||
@@ -594,6 +629,13 @@ class AccountChartTemplate(models.AbstractModel):
|
||||
template_data[model].update(data)
|
||||
else:
|
||||
for xmlid, record in data.items():
|
||||
# Store information about which module each field value originates from (for code translations).
|
||||
# The final value of different fields may be determined by different functions.
|
||||
# The last function to modify the record may not modify all or any of the translatable fields.
|
||||
for field in translatable_fields + untranslatable_fields:
|
||||
if field in record:
|
||||
record.setdefault('__translation_module__', {})[field] = func._module
|
||||
|
||||
template_data[model][xmlid].update(record)
|
||||
return template_data
|
||||
|
||||
@@ -669,7 +711,14 @@ class AccountChartTemplate(models.AbstractModel):
|
||||
for company_attr_name in accounts_data:
|
||||
company[company_attr_name] = company.parent_ids[0][company_attr_name]
|
||||
else:
|
||||
accounts = self.env['account.account'].create(accounts_data.values())
|
||||
accounts = self.env['account.account']._load_records([
|
||||
{
|
||||
'xml_id': f"account.{str(self.env.company.id)}_{xml_id}",
|
||||
'values': values,
|
||||
'noupdate': True,
|
||||
}
|
||||
for xml_id, values in accounts_data.items()
|
||||
])
|
||||
for company_attr_name, account in zip(accounts_data.keys(), accounts):
|
||||
company[company_attr_name] = account
|
||||
|
||||
@@ -1031,6 +1080,127 @@ class AccountChartTemplate(models.AbstractModel):
|
||||
_logger.debug("No file %s found for template '%s'", model, module)
|
||||
return res
|
||||
|
||||
def _get_untranslatable_fields_target_language(self, template_code, company):
|
||||
"""Return the code of the language we want to translate the untranslatable fields into.
|
||||
"""
|
||||
# Note: In case this function is called during module installation
|
||||
# * The active user is the super user.
|
||||
# * There is no 'lang' in the context.
|
||||
return company.partner_id.lang or get_lang(self.env).code
|
||||
|
||||
def _get_untranslatable_fields_to_translate(self):
|
||||
"""Return information about the untranslatable fields we want to translate anyway.
|
||||
|
||||
:param langs: The codes of the languages into which we want to translate the records.
|
||||
:type langs: list[str]
|
||||
:param companies: Records belonging to these companies will be considered.
|
||||
:type companies: Model<res.company>
|
||||
:return: Dictionary (model -> list of fields) where the list of fields contains
|
||||
all the untranslatable fields of the model we want to translate anyway
|
||||
:rtype: dict[str, list[str]]
|
||||
"""
|
||||
return {
|
||||
'account.journal': [
|
||||
'code',
|
||||
],
|
||||
}
|
||||
|
||||
def _get_translatable_template_model_fields(self):
|
||||
return {
|
||||
model: [fieldname for (fieldname, field) in self.env[model]._fields.items() if field.translate]
|
||||
for model in TEMPLATE_MODELS
|
||||
}
|
||||
|
||||
def _get_untranslated_translatable_template_model_records(self, langs, companies):
|
||||
"""Return information about the records of any model in TEMPLATE_MODELS (and belonging to companies) that need to be translated.
|
||||
Records are in need of translation if they have a translatable field which is missing a translation (into any of the languages given in langs).
|
||||
|
||||
:param langs: The codes of the languages into which we want to translate the records.
|
||||
:type langs: list[str]
|
||||
:param companies: Records belonging to these companies will be considered.
|
||||
:type companies: Model<res.company>
|
||||
:return: The records which information will be returned are those records that have at least 1 untranslated translatable field.
|
||||
A field is 'untranslated' if it does not have a translation for all languages in langs.
|
||||
The returned value is a List of tuples:
|
||||
(model, xmlid (without module prefix), module, dictionary from name to value for each translatable field)
|
||||
:rtype: list[tuple(str, str, str, dict[str, str])]
|
||||
"""
|
||||
if not langs or not companies:
|
||||
return []
|
||||
|
||||
company_ids = tuple(companies.ids)
|
||||
|
||||
translatable_model_fields = self._get_translatable_template_model_fields()
|
||||
|
||||
# Generate a list of queries; exactly 1 per model
|
||||
queries = []
|
||||
for model in TEMPLATE_MODELS:
|
||||
translatable_fields = translatable_model_fields[model]
|
||||
if not translatable_fields:
|
||||
continue
|
||||
|
||||
self.env[model].flush_model(['id', 'company_id'] + translatable_model_fields[model])
|
||||
|
||||
# We only want records that have at least 1 missing translation in any of its translatable fields
|
||||
missing_translation_clauses = [
|
||||
SQL("(%s ->> %s) IS NULL", SQL.identifier('model', field), lang)
|
||||
for field in translatable_fields
|
||||
for lang in langs
|
||||
]
|
||||
|
||||
translatable_field_column_args = []
|
||||
for field in translatable_fields:
|
||||
translatable_field_column_args.extend((SQL("%s", field), SQL.identifier('model', field)))
|
||||
|
||||
queries.append(SQL(
|
||||
"""
|
||||
SELECT %(model)s AS model,
|
||||
model_data.name AS xmlid,
|
||||
model_data.module AS module,
|
||||
json_build_object(%(translatable_field_column_args)s) AS fields
|
||||
FROM %(table)s model
|
||||
JOIN ir_model_data model_data ON model_data.model = %(model)s
|
||||
AND model.id = model_data.res_id
|
||||
WHERE (%(missing_translation_clauses)s)
|
||||
AND model.company_id IN %(company_ids)s
|
||||
""",
|
||||
model=model,
|
||||
translatable_field_column_args=SQL(", ").join(translatable_field_column_args),
|
||||
table=SQL.identifier(self.env[model]._table),
|
||||
company_ids=company_ids,
|
||||
missing_translation_clauses=SQL(" OR ").join(missing_translation_clauses),
|
||||
))
|
||||
|
||||
query = (SQL(' UNION ALL ').join(queries))
|
||||
# the queried models have been flushed already as part of the loop building the queries per model
|
||||
self.env['ir.model.data'].flush_model(['res_id', 'model', 'name'])
|
||||
|
||||
self._cr.execute(query)
|
||||
return self._cr.fetchall()
|
||||
|
||||
def _get_field_translation(self, record, fname, lang):
|
||||
"""Return the value for language lang for field with fname from record (or None if none exists).
|
||||
|
||||
:param record: record formatted like in the template data (generated by _get_chart_template_data)
|
||||
:type record: dict
|
||||
:param fname: the name of a field (in record) as string
|
||||
:type str
|
||||
:param lang: the code of a res.lang
|
||||
:type str
|
||||
:return record[fname] translated into lang (or None)
|
||||
:rtype str
|
||||
"""
|
||||
generic_lang = lang.split('_')[0] # manage generic locale (i.e. `fr` instead of `fr_BE`)
|
||||
translation_module = record.get('__translation_module__', {}).get(fname, 'account')
|
||||
translation = record.get(f"{fname}@{lang}") or record.get(f"{fname}@{generic_lang}")
|
||||
if translation or fname not in record:
|
||||
return translation
|
||||
else:
|
||||
return (
|
||||
code_translations.get_python_translations(translation_module, lang).get(record[fname])
|
||||
or code_translations.get_python_translations(translation_module, generic_lang).get(record[fname])
|
||||
)
|
||||
|
||||
def _load_translations(self, langs=None, companies=None):
|
||||
"""Load the translations of the chart template.
|
||||
|
||||
@@ -1045,19 +1215,43 @@ class AccountChartTemplate(models.AbstractModel):
|
||||
companies = companies or self.env['res.company'].search([('chart_template', 'in', available_template_codes)])
|
||||
|
||||
translation_importer = TranslationImporter(self.env.cr, verbose=False)
|
||||
|
||||
# Gather translations for records that are created from the chart_template data
|
||||
for chart_template, chart_companies in groupby(companies, lambda c: c.chart_template):
|
||||
template_data = self.env['account.chart.template']._get_chart_template_data(chart_template)
|
||||
template_data.pop('template_data', None)
|
||||
for mname, data in template_data.items():
|
||||
for _xml_id, record in data.items():
|
||||
fnames = {fname.split('@')[0] for fname in record}
|
||||
fnames = {fname.split('@')[0] for fname in record if fname != '__translation_module__'}
|
||||
for lang in langs:
|
||||
for fname in fnames:
|
||||
value = record.get(f"{fname}@{lang}")
|
||||
if not value: # manage generic locale (i.e. `fr` instead of `fr_BE`)
|
||||
value = record.get(f"{fname}@{lang.split('_')[0]}")
|
||||
if value:
|
||||
field = self.env[mname]._fields.get(fname)
|
||||
if not field or not field.translate:
|
||||
continue
|
||||
field_translation = self._get_field_translation(record, fname, lang)
|
||||
if field_translation:
|
||||
for company in chart_companies:
|
||||
xml_id = f"account.{company.id}_{_xml_id}"
|
||||
translation_importer.model_translations[mname][fname][xml_id][lang] = value
|
||||
translation_importer.model_translations[mname][fname][xml_id][lang] = field_translation
|
||||
|
||||
# Gather translations for the TEMPLATE_MODELS records that are not created from the chart_template data
|
||||
translation_langs = [lang for lang in langs if lang != 'en_US'] # there are no code translations for 'en_US' (original language)
|
||||
for (mname, _xml_id, module, fields) in self._get_untranslated_translatable_template_model_records(translation_langs, companies):
|
||||
for (field, value) in fields.items():
|
||||
if not value or 'en_US' not in value:
|
||||
continue
|
||||
value_en_US = value['en_US']
|
||||
xml_id = f"{module}.{_xml_id}"
|
||||
for lang in [lang for lang in translation_langs if lang not in value]:
|
||||
if lang in translation_importer.model_translations[mname][field][xml_id]:
|
||||
continue
|
||||
value_translated = None
|
||||
for code_module in ([module, 'account'] if module != 'account' else ['account']):
|
||||
value_translated = code_translations.get_python_translations(code_module, lang).get(value_en_US)
|
||||
if not value_translated: # manage generic locale (i.e. `fr` instead of `fr_BE`)
|
||||
value_translated = code_translations.get_python_translations(code_module, lang.split('_')[0]).get(value_en_US)
|
||||
if value_translated:
|
||||
translation_importer.model_translations[mname][field][xml_id][lang] = value_translated
|
||||
break
|
||||
|
||||
translation_importer.save(overwrite=False)
|
||||
|
||||
@@ -470,12 +470,18 @@ class ResCompany(models.Model):
|
||||
('code', '=', str(code)),
|
||||
]):
|
||||
code -= 1
|
||||
return self.env['account.account'].create({
|
||||
'code': str(code),
|
||||
'name': _('Undistributed Profits/Losses'),
|
||||
'account_type': unaffected_earnings_type,
|
||||
'company_id': self.id,
|
||||
})
|
||||
return self.env['account.account']._load_records([
|
||||
{
|
||||
'xml_id': f"account.{str(self.id)}_unaffected_earnings_account",
|
||||
'values': {
|
||||
'code': str(code),
|
||||
'name': _('Undistributed Profits/Losses'),
|
||||
'account_type': unaffected_earnings_type,
|
||||
'company_id': self.id,
|
||||
},
|
||||
'noupdate': True,
|
||||
}
|
||||
])
|
||||
|
||||
def get_opening_move_differences(self, opening_move_lines):
|
||||
# TO BE REMOVED IN MASTER
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from odoo import Command
|
||||
from odoo.addons.account.models.chart_template import code_translations
|
||||
from odoo.addons.account.models.chart_template import AccountChartTemplate
|
||||
from odoo.addons.account.models.chart_template import TEMPLATE_MODELS
|
||||
from odoo.addons.account.tests.common import instantiate_accountman
|
||||
@@ -550,3 +551,177 @@ class TestChartTemplate(TransactionCase):
|
||||
}
|
||||
with self.assertRaisesRegex(UserError, 'update your localization'):
|
||||
self.env['account.chart.template']._deref_account_tags('test', {'tax1': tax_to_load})
|
||||
|
||||
def test_install_with_translations(self):
|
||||
""" Ensure that the translations are loaded correctly when installing chart data; i.e. test '_load_translations' and that the untranslatable fields are translated correctly.
|
||||
Note: The '_load_translations' function depends on the '_get_chart_template_data' function for some information.
|
||||
The result of '_get_chart_template_data' is mocked (correctly) in this test (and not tested).
|
||||
"""
|
||||
|
||||
# Local mock for '_get_chart_template_mapping'
|
||||
# We will use / install a dedicated new chart 'translation' (not just reload 'test')
|
||||
# To have control over the original / en_US values.
|
||||
def local_get_mapping(self, get_all=False):
|
||||
return {'translation': {
|
||||
'name': 'translation',
|
||||
'country_id': None,
|
||||
'country_code': None,
|
||||
'modules': ['account'],
|
||||
'parent': None,
|
||||
}}
|
||||
|
||||
company = self.company_1
|
||||
|
||||
# Create records that are not part of the chart template
|
||||
# They will be translated via code translations.
|
||||
# The module used to source the translation is the module from the xml_id or 'account' (as fallback)
|
||||
|
||||
non_chart_data = {
|
||||
'account.group': {
|
||||
# try module 'no_translation'; fallback to 'account'
|
||||
'no_translation.test_chart_template_company_test_free_account_group': {
|
||||
'name': 'Free Account Group',
|
||||
'code_prefix_start': 333330,
|
||||
'code_prefix_end': 333339,
|
||||
'company_id': company.id,
|
||||
},
|
||||
},
|
||||
'account.account': {
|
||||
# translate via 'translation' module
|
||||
'translation.test_chart_template_company_test_free_account': {
|
||||
'name': 'Free Account',
|
||||
'code': '333331',
|
||||
'account_type': 'asset_current',
|
||||
'company_id': company.id,
|
||||
},
|
||||
},
|
||||
'account.tax': {
|
||||
# translate via 'translation' module;
|
||||
# 2 translatable fields ('name' and 'description')
|
||||
'translation.test_chart_template_company_test_free_tax': {
|
||||
"name": "Free Tax",
|
||||
"description": "Free Tax Description",
|
||||
"amount": "0.00",
|
||||
"company_id": company.id,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# Local function to "extend" '_post_load_data' to ensure the creation of the records from 'non_chart_data'
|
||||
def test_post_load_data(template_code, company, template_data):
|
||||
for model, data in non_chart_data.items():
|
||||
for xml_id, values in data.items():
|
||||
self.env[model]._load_records([{
|
||||
'xml_id': xml_id,
|
||||
'values': values,
|
||||
}])
|
||||
|
||||
# Create a local mock of '_get_chart_template_data'; "extend" 'test_get_data' with the translation info
|
||||
|
||||
translation_update_for_test_get_data = {
|
||||
# Use code translations from module 'translation'
|
||||
'account.journal': {
|
||||
'cash': {
|
||||
'name': "Cash",
|
||||
'code': "C", # untranslatable field; shortened due to length restriction (for _translation)
|
||||
'__translation_module__': {
|
||||
'name': 'translation',
|
||||
'code': 'translation',
|
||||
},
|
||||
},
|
||||
},
|
||||
# Different modules for code translations of 'name' and 'description'
|
||||
'account.tax': {
|
||||
'test_tax_1_template': {
|
||||
'name': "Tax 1",
|
||||
'description': "Tax 1 Description",
|
||||
'__translation_module__': {
|
||||
'name': 'translation',
|
||||
'description': 'translation2',
|
||||
},
|
||||
},
|
||||
},
|
||||
# Use 'name@' and not code translation
|
||||
'account.tax.group': {
|
||||
'tax_group_taxes': {
|
||||
'name': "Taxes",
|
||||
'name@fr': "Taxes FR",
|
||||
'__translation_module__': {
|
||||
'name': 'translation',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def local_get_data(self, template_code):
|
||||
data = test_get_data(self, template_code)
|
||||
for model, record_info in translation_update_for_test_get_data.items():
|
||||
for xmlid, data_update in record_info.items():
|
||||
data[model][xmlid].update(data_update)
|
||||
return data
|
||||
|
||||
# Tranlations should fall back to more generic locale 'fr'
|
||||
|
||||
# Target lang for untranslatable fields
|
||||
company.partner_id.lang = self.env['res.lang']._activate_lang('fr_BE').code
|
||||
|
||||
# Init empty mock translations to make sure we do not use unintended translation
|
||||
mock_python_translations = {}
|
||||
|
||||
for module, lang, value, translation in [
|
||||
# wrong translations
|
||||
('translation', 'fr', "Taxes", "WRONG"), # there is a value in the chart data
|
||||
('translation', 'fr', "Free Account", "Free Account FR"), # there is a value for fr_BE
|
||||
# correct translations
|
||||
('translation', 'fr', "Cash", "Cash FR"),
|
||||
('translation', 'fr', "C", "C FR"),
|
||||
('translation', 'fr', "Tax 1", "Tax 1 FR"),
|
||||
('translation', 'fr_BE', "Free Account", "Free Account FR_BE"),
|
||||
('translation', 'fr', "Free Tax", "Free Tax FR"),
|
||||
('translation', 'fr', "Free Tax Description", "Free Tax Description FR"),
|
||||
('translation2', 'fr', "Tax 1 Description", "Tax 1 Description translation2/FR"),
|
||||
('account', 'fr', "Free Account Group", "Free Account Group account/FR"),
|
||||
]:
|
||||
mock_python_translations.setdefault((module, lang), {})[value] = translation
|
||||
|
||||
with patch.object(AccountChartTemplate, '_get_chart_template_mapping', side_effect=local_get_mapping, autospec=True):
|
||||
with patch.object(AccountChartTemplate, '_get_chart_template_data', side_effect=local_get_data, autospec=True):
|
||||
with patch.object(AccountChartTemplate, '_post_load_data', wraps=test_post_load_data):
|
||||
with patch.object(code_translations, 'python_translations', mock_python_translations):
|
||||
self.env['account.chart.template'].try_loading('translation', company=company, install_demo=False)
|
||||
|
||||
# Check translations
|
||||
translatable_model_fields = self.env['account.chart.template']._get_translatable_template_model_fields()
|
||||
untranslatable_model_fields = self.env['account.chart.template']._get_untranslatable_fields_to_translate()
|
||||
fields_to_translate = {
|
||||
model: set(translatable_model_fields.get(model, []) + untranslatable_model_fields.get(model, []))
|
||||
for model in TEMPLATE_MODELS
|
||||
}
|
||||
|
||||
self.assertEqual({
|
||||
f'{xmlid}.{field}@{lang}': self.env['account.chart.template'].ref(xmlid).with_context(lang=lang)[field]
|
||||
for chart_like_data in [non_chart_data, translation_update_for_test_get_data]
|
||||
for model, data in chart_like_data.items()
|
||||
for xmlid, record_data in data.items()
|
||||
for field in record_data if field in fields_to_translate.get(model, set())
|
||||
for lang in ['en_US', 'fr_BE']
|
||||
}, {
|
||||
'cash.code@en_US': 'C FR', # untranslatable field loaded in lang fr_BE
|
||||
'cash.code@fr_BE': 'C FR',
|
||||
'cash.name@en_US': 'Cash',
|
||||
'cash.name@fr_BE': 'Cash FR',
|
||||
'no_translation.test_chart_template_company_test_free_account_group.name@en_US': 'Free Account Group',
|
||||
'no_translation.test_chart_template_company_test_free_account_group.name@fr_BE': 'Free Account Group account/FR', # fallback to account
|
||||
'tax_group_taxes.name@en_US': 'Taxes',
|
||||
'tax_group_taxes.name@fr_BE': 'Taxes FR',
|
||||
'test_tax_1_template.description@en_US': 'Tax 1 Description',
|
||||
'test_tax_1_template.description@fr_BE': 'Tax 1 Description translation2/FR',
|
||||
'test_tax_1_template.name@en_US': 'Tax 1',
|
||||
'test_tax_1_template.name@fr_BE': 'Tax 1 FR',
|
||||
'translation.test_chart_template_company_test_free_account.name@en_US': 'Free Account',
|
||||
'translation.test_chart_template_company_test_free_account.name@fr_BE': 'Free Account FR_BE', # do not use generic lang
|
||||
'translation.test_chart_template_company_test_free_tax.description@en_US': 'Free Tax Description',
|
||||
'translation.test_chart_template_company_test_free_tax.description@fr_BE': 'Free Tax Description FR',
|
||||
'translation.test_chart_template_company_test_free_tax.name@en_US': 'Free Tax',
|
||||
'translation.test_chart_template_company_test_free_tax.name@fr_BE': 'Free Tax FR',
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user