[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:
Sven Fuehr
2024-03-21 23:02:58 +00:00
parent dd998b5717
commit b92406420d
4 changed files with 402 additions and 19 deletions
+9 -1
View File
@@ -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:
+206 -12
View File
@@ -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)
+12 -6
View File
@@ -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
+175
View File
@@ -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',
})