mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 08:32:03 +02:00
18.0 vanilla
This commit is contained in:
parent
d72e748793
commit
0a7ae8db93
337 changed files with 399651 additions and 232598 deletions
|
|
@ -1,6 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
# When using quotation marks in translation strings, please use curly quotes (“”)
|
||||
# instead of straight quotes (""). On Linux, the keyboard shortcuts are:
|
||||
# AltGr + V for the opening curly quotes “
|
||||
# AltGr + B for the closing curly quotes ”
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import codecs
|
||||
|
|
@ -13,18 +17,18 @@ import json
|
|||
import locale
|
||||
import logging
|
||||
import os
|
||||
from tokenize import generate_tokens, STRING, NEWLINE, INDENT, DEDENT
|
||||
import polib
|
||||
import re
|
||||
import tarfile
|
||||
import threading
|
||||
import typing
|
||||
import warnings
|
||||
from collections import defaultdict, namedtuple
|
||||
from contextlib import suppress
|
||||
from datetime import datetime
|
||||
from os.path import join
|
||||
|
||||
from pathlib import Path
|
||||
from tokenize import generate_tokens, STRING, NEWLINE, INDENT, DEDENT
|
||||
|
||||
from babel.messages import extract
|
||||
from lxml import etree, html
|
||||
from markupsafe import escape, Markup
|
||||
|
|
@ -32,8 +36,15 @@ from psycopg2.extras import Json
|
|||
|
||||
import odoo
|
||||
from odoo.exceptions import UserError
|
||||
from . import config, pycompat
|
||||
from .misc import file_open, file_path, get_iso_codes, SKIPPED_ELEMENT_TYPES
|
||||
from .config import config
|
||||
from .misc import file_open, file_path, get_iso_codes, OrderedSet, ReadonlyDict, SKIPPED_ELEMENT_TYPES
|
||||
|
||||
__all__ = [
|
||||
"_",
|
||||
"LazyTranslate",
|
||||
"html_translate",
|
||||
"xml_translate",
|
||||
]
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -41,9 +52,6 @@ PYTHON_TRANSLATION_COMMENT = 'odoo-python'
|
|||
|
||||
# translation used for javascript code in web client
|
||||
JAVASCRIPT_TRANSLATION_COMMENT = 'odoo-javascript'
|
||||
# used to notify web client that these translations should be loaded in the UI
|
||||
# deprecated comment since Odoo 16.0
|
||||
WEB_TRANSLATION_COMMENT = "openerp-web"
|
||||
|
||||
SKIPPED_ELEMENTS = ('script', 'style', 'title')
|
||||
|
||||
|
|
@ -141,12 +149,6 @@ class UNIX_LINE_TERMINATOR(csv.excel):
|
|||
csv.register_dialect("UNIX", UNIX_LINE_TERMINATOR)
|
||||
|
||||
|
||||
# FIXME: holy shit this whole thing needs to be cleaned up hard it's a mess
|
||||
def encode(s):
|
||||
assert isinstance(s, str)
|
||||
return s
|
||||
|
||||
|
||||
# which elements are translated inline
|
||||
TRANSLATED_ELEMENTS = {
|
||||
'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'data', 'del', 'dfn', 'em',
|
||||
|
|
@ -161,7 +163,7 @@ TRANSLATED_ELEMENTS = {
|
|||
TRANSLATED_ATTRS = dict.fromkeys({
|
||||
'string', 'add-label', 'help', 'sum', 'avg', 'confirm', 'placeholder', 'alt', 'title', 'aria-label',
|
||||
'aria-keyshortcuts', 'aria-placeholder', 'aria-roledescription', 'aria-valuetext',
|
||||
'value_label', 'data-tooltip', 'data-editor-message', 'label', 'cancel-label', 'confirm-label',
|
||||
'value_label', 'data-tooltip', 'label', 'cancel-label', 'confirm-label',
|
||||
}, lambda e: True)
|
||||
|
||||
def translate_attrib_value(node):
|
||||
|
|
@ -317,7 +319,7 @@ def serialize_xml(node):
|
|||
return etree.tostring(node, method='xml', encoding='unicode')
|
||||
|
||||
|
||||
MODIFIER_ATTRS = {"invisible", "readonly", "required", "column_invisible", "attrs", "states"}
|
||||
MODIFIER_ATTRS = {"invisible", "readonly", "required", "column_invisible", "attrs"}
|
||||
def xml_term_adapter(term_en):
|
||||
"""
|
||||
Returns an `adapter(term)` function that will ensure the modifiers are copied
|
||||
|
|
@ -453,158 +455,218 @@ def translate_sql_constraint(cr, key, lang):
|
|||
""", (lang, key))
|
||||
return cr.fetchone()[0]
|
||||
|
||||
class GettextAlias(object):
|
||||
|
||||
def _get_db(self):
|
||||
# find current DB based on thread/worker db name (see netsvc)
|
||||
db_name = getattr(threading.current_thread(), 'dbname', None)
|
||||
if db_name:
|
||||
return odoo.sql_db.db_connect(db_name)
|
||||
|
||||
def _get_cr(self, frame, allow_create=True):
|
||||
# try, in order: cr, cursor, self.env.cr, self.cr,
|
||||
# request.env.cr
|
||||
if 'cr' in frame.f_locals:
|
||||
return frame.f_locals['cr'], False
|
||||
if 'cursor' in frame.f_locals:
|
||||
return frame.f_locals['cursor'], False
|
||||
s = frame.f_locals.get('self')
|
||||
if hasattr(s, 'env'):
|
||||
return s.env.cr, False
|
||||
if hasattr(s, 'cr'):
|
||||
return s.cr, False
|
||||
try:
|
||||
from odoo.http import request
|
||||
return request.env.cr, False
|
||||
except RuntimeError:
|
||||
pass
|
||||
if allow_create:
|
||||
# create a new cursor
|
||||
db = self._get_db()
|
||||
if db is not None:
|
||||
return db.cursor(), True
|
||||
return None, False
|
||||
|
||||
def _get_uid(self, frame):
|
||||
# try, in order: uid, user, self.env.uid
|
||||
if 'uid' in frame.f_locals:
|
||||
return frame.f_locals['uid']
|
||||
if 'user' in frame.f_locals:
|
||||
return int(frame.f_locals['user']) # user may be a record
|
||||
s = frame.f_locals.get('self')
|
||||
return s.env.uid
|
||||
|
||||
def _get_lang(self, frame):
|
||||
# try, in order: context.get('lang'), kwargs['context'].get('lang'),
|
||||
# self.env.lang, self.localcontext.get('lang'), request.env.lang
|
||||
lang = None
|
||||
if frame.f_locals.get('context'):
|
||||
lang = frame.f_locals['context'].get('lang')
|
||||
if not lang:
|
||||
kwargs = frame.f_locals.get('kwargs', {})
|
||||
if kwargs.get('context'):
|
||||
lang = kwargs['context'].get('lang')
|
||||
if not lang:
|
||||
s = frame.f_locals.get('self')
|
||||
if hasattr(s, 'env'):
|
||||
lang = s.env.lang
|
||||
if not lang:
|
||||
if hasattr(s, 'localcontext'):
|
||||
lang = s.localcontext.get('lang')
|
||||
if not lang:
|
||||
try:
|
||||
from odoo.http import request
|
||||
lang = request.env.lang
|
||||
except RuntimeError:
|
||||
pass
|
||||
if not lang:
|
||||
# Last resort: attempt to guess the language of the user
|
||||
# Pitfall: some operations are performed in sudo mode, and we
|
||||
# don't know the original uid, so the language may
|
||||
# be wrong when the admin language differs.
|
||||
(cr, dummy) = self._get_cr(frame, allow_create=False)
|
||||
uid = self._get_uid(frame)
|
||||
if cr and uid:
|
||||
env = odoo.api.Environment(cr, uid, {})
|
||||
lang = env['res.users'].context_get()['lang']
|
||||
return lang
|
||||
|
||||
def __call__(self, source, *args, **kwargs):
|
||||
translation = self._get_translation(source)
|
||||
assert not (args and kwargs)
|
||||
if args or kwargs:
|
||||
if any(isinstance(a, Markup) for a in itertools.chain(args, kwargs.values())):
|
||||
translation = escape(translation)
|
||||
try:
|
||||
return translation % (args or kwargs)
|
||||
except (TypeError, ValueError, KeyError):
|
||||
bad = translation
|
||||
# fallback: apply to source before logging exception (in case source fails)
|
||||
translation = source % (args or kwargs)
|
||||
_logger.exception('Bad translation %r for string %r', bad, source)
|
||||
def get_translation(module: str, lang: str, source: str, args: tuple | dict) -> str:
|
||||
"""Translate and format using a module, language, source text and args."""
|
||||
# get the translation by using the language
|
||||
assert lang, "missing language for translation"
|
||||
if lang == 'en_US':
|
||||
translation = source
|
||||
else:
|
||||
assert module, "missing module name for translation"
|
||||
translation = code_translations.get_python_translations(module, lang).get(source, source)
|
||||
# skip formatting if we have no args
|
||||
if not args:
|
||||
return translation
|
||||
# we need to check the args for markup values and for lazy translations
|
||||
args_is_dict = isinstance(args, dict)
|
||||
if any(isinstance(a, Markup) for a in (args.values() if args_is_dict else args)):
|
||||
translation = escape(translation)
|
||||
if any(isinstance(a, LazyGettext) for a in (args.values() if args_is_dict else args)):
|
||||
if args_is_dict:
|
||||
args = {k: v._translate(lang) if isinstance(v, LazyGettext) else v for k, v in args.items()}
|
||||
else:
|
||||
args = tuple(v._translate(lang) if isinstance(v, LazyGettext) else v for v in args)
|
||||
# format
|
||||
try:
|
||||
return translation % args
|
||||
except (TypeError, ValueError, KeyError):
|
||||
bad = translation
|
||||
# fallback: apply to source before logging exception (in case source fails)
|
||||
translation = source % args
|
||||
_logger.exception('Bad translation %r for string %r', bad, source)
|
||||
return translation
|
||||
|
||||
def _get_translation(self, source, module=None):
|
||||
try:
|
||||
frame = inspect.currentframe().f_back.f_back
|
||||
lang = self._get_lang(frame)
|
||||
if lang and lang != 'en_US':
|
||||
if not module:
|
||||
path = inspect.getfile(frame)
|
||||
path_info = odoo.modules.get_resource_from_path(path)
|
||||
module = path_info[0] if path_info else 'base'
|
||||
return code_translations.get_python_translations(module, lang).get(source, source)
|
||||
else:
|
||||
_logger.debug('no translation language detected, skipping translation for "%r" ', source)
|
||||
except Exception:
|
||||
_logger.debug('translation went wrong for "%r", skipped', source)
|
||||
# if so, double-check the root/base translations filenames
|
||||
return source
|
||||
|
||||
def get_translated_module(arg: str | int | typing.Any) -> str: # frame not represented as hint
|
||||
"""Get the addons name.
|
||||
|
||||
:param arg: can be any of the following:
|
||||
str ("name_of_module") returns itself;
|
||||
str (__name__) use to resolve module name;
|
||||
int is number of frames to go back to the caller;
|
||||
frame of the caller function
|
||||
"""
|
||||
if isinstance(arg, str):
|
||||
if arg.startswith('odoo.addons.'):
|
||||
# get the name of the module
|
||||
return arg.split('.')[2]
|
||||
if '.' in arg or not arg:
|
||||
# module name is not in odoo.addons.
|
||||
return 'base'
|
||||
else:
|
||||
return arg
|
||||
else:
|
||||
if isinstance(arg, int):
|
||||
frame = inspect.currentframe()
|
||||
while arg > 0:
|
||||
arg -= 1
|
||||
frame = frame.f_back
|
||||
else:
|
||||
frame = arg
|
||||
if not frame:
|
||||
return 'base'
|
||||
if (module_name := frame.f_globals.get("__name__")) and module_name.startswith('odoo.addons.'):
|
||||
# just a quick lookup because `get_resource_from_path is slow compared to this`
|
||||
return module_name.split('.')[2]
|
||||
path = inspect.getfile(frame)
|
||||
path_info = odoo.modules.get_resource_from_path(path)
|
||||
return path_info[0] if path_info else 'base'
|
||||
|
||||
|
||||
def _get_cr(frame):
|
||||
# try, in order: cr, cursor, self.env.cr, self.cr,
|
||||
# request.env.cr
|
||||
if 'cr' in frame.f_locals:
|
||||
return frame.f_locals['cr']
|
||||
if 'cursor' in frame.f_locals:
|
||||
return frame.f_locals['cursor']
|
||||
if (local_self := frame.f_locals.get('self')) is not None:
|
||||
if (local_env := getattr(local_self, 'env', None)) is not None:
|
||||
return local_env.cr
|
||||
if (cr := getattr(local_self, 'cr', None)) is not None:
|
||||
return cr
|
||||
try:
|
||||
from odoo.http import request # noqa: PLC0415
|
||||
request_env = request.env
|
||||
if request_env is not None and (cr := request_env.cr) is not None:
|
||||
return cr
|
||||
except RuntimeError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _get_uid(frame) -> int | None:
|
||||
# try, in order: uid, user, self.env.uid
|
||||
if 'uid' in frame.f_locals:
|
||||
return frame.f_locals['uid']
|
||||
if 'user' in frame.f_locals:
|
||||
return int(frame.f_locals['user']) # user may be a record
|
||||
if (local_self := frame.f_locals.get('self')) is not None:
|
||||
if hasattr(local_self, 'env') and (uid := local_self.env.uid):
|
||||
return uid
|
||||
return None
|
||||
|
||||
|
||||
def _get_lang(frame, default_lang='') -> str:
|
||||
# get from: context.get('lang'), kwargs['context'].get('lang'),
|
||||
if local_context := frame.f_locals.get('context'):
|
||||
if lang := local_context.get('lang'):
|
||||
return lang
|
||||
if (local_kwargs := frame.f_locals.get('kwargs')) and (local_context := local_kwargs.get('context')):
|
||||
if lang := local_context.get('lang'):
|
||||
return lang
|
||||
# get from self.env
|
||||
log_level = logging.WARNING
|
||||
local_self = frame.f_locals.get('self')
|
||||
local_env = local_self is not None and getattr(local_self, 'env', None)
|
||||
if local_env:
|
||||
if lang := local_env.lang:
|
||||
return lang
|
||||
# we found the env, in case we fail, just log in debug
|
||||
log_level = logging.DEBUG
|
||||
# get from request?
|
||||
try:
|
||||
from odoo.http import request # noqa: PLC0415
|
||||
request_env = request.env
|
||||
if request_env and (lang := request_env.lang):
|
||||
return lang
|
||||
except RuntimeError:
|
||||
pass
|
||||
# Last resort: attempt to guess the language of the user
|
||||
# Pitfall: some operations are performed in sudo mode, and we
|
||||
# don't know the original uid, so the language may
|
||||
# be wrong when the admin language differs.
|
||||
cr = _get_cr(frame)
|
||||
uid = _get_uid(frame)
|
||||
if cr and uid:
|
||||
env = odoo.api.Environment(cr, uid, {})
|
||||
if lang := env['res.users'].context_get().get('lang'):
|
||||
return lang
|
||||
# fallback
|
||||
if default_lang:
|
||||
_logger.debug('no translation language detected, fallback to %s', default_lang)
|
||||
return default_lang
|
||||
# give up
|
||||
_logger.log(log_level, 'no translation language detected, skipping translation %s', frame, stack_info=True)
|
||||
return ''
|
||||
|
||||
|
||||
def _get_translation_source(stack_level: int, module: str = '', lang: str = '', default_lang: str = '') -> tuple[str, str]:
|
||||
if not (module and lang):
|
||||
frame = inspect.currentframe()
|
||||
for _index in range(stack_level + 1):
|
||||
frame = frame.f_back
|
||||
lang = lang or _get_lang(frame, default_lang)
|
||||
if lang and lang != 'en_US':
|
||||
return get_translated_module(module or frame), lang
|
||||
else:
|
||||
# we don't care about the module for 'en_US'
|
||||
return module or 'base', 'en_US'
|
||||
|
||||
|
||||
def get_text_alias(source: str, *args, **kwargs):
|
||||
assert not (args and kwargs)
|
||||
assert isinstance(source, str)
|
||||
module, lang = _get_translation_source(1)
|
||||
return get_translation(module, lang, source, args or kwargs)
|
||||
|
||||
|
||||
@functools.total_ordering
|
||||
class _lt:
|
||||
""" Lazy code translation
|
||||
class LazyGettext:
|
||||
""" Lazy code translated term.
|
||||
|
||||
Similar to GettextAlias but the translation lookup will be done only at
|
||||
Similar to get_text_alias but the translation lookup will be done only at
|
||||
__str__ execution.
|
||||
This eases the search for terms to translate as lazy evaluated strings
|
||||
are declared early.
|
||||
|
||||
A code using translated global variables such as:
|
||||
|
||||
```
|
||||
_lt = LazyTranslate(__name__)
|
||||
LABEL = _lt("User")
|
||||
|
||||
def _compute_label(self):
|
||||
context = {'lang': self.partner_id.lang}
|
||||
self.user_label = LABEL
|
||||
env = self.with_env(lang=self.partner_id.lang).env
|
||||
self.user_label = env._(LABEL)
|
||||
```
|
||||
|
||||
works as expected (unlike the classic GettextAlias implementation).
|
||||
works as expected (unlike the classic get_text_alias implementation).
|
||||
"""
|
||||
|
||||
__slots__ = ['_source', '_args', '_module']
|
||||
__slots__ = ('_args', '_default_lang', '_module', '_source')
|
||||
|
||||
def __init__(self, source, *args, **kwargs):
|
||||
self._source = source
|
||||
def __init__(self, source, *args, _module='', _default_lang='', **kwargs):
|
||||
assert not (args and kwargs)
|
||||
assert isinstance(source, str)
|
||||
self._source = source
|
||||
self._args = args or kwargs
|
||||
self._module = get_translated_module(_module or 2)
|
||||
self._default_lang = _default_lang
|
||||
|
||||
frame = inspect.currentframe().f_back
|
||||
path = inspect.getfile(frame)
|
||||
path_info = odoo.modules.get_resource_from_path(path)
|
||||
self._module = path_info[0] if path_info else 'base'
|
||||
def _translate(self, lang: str = '') -> str:
|
||||
module, lang = _get_translation_source(2, self._module, lang, default_lang=self._default_lang)
|
||||
return get_translation(module, lang, self._source, self._args)
|
||||
|
||||
def __repr__(self):
|
||||
""" Show for the debugger"""
|
||||
args = {'_module': self._module, '_default_lang': self._default_lang, '_args': self._args}
|
||||
return f"_lt({self._source!r}, **{args!r})"
|
||||
|
||||
def __str__(self):
|
||||
# Call _._get_translation() like _() does, so that we have the same number
|
||||
# of stack frames calling _get_translation()
|
||||
translation = _._get_translation(self._source, self._module)
|
||||
if self._args:
|
||||
try:
|
||||
return translation % self._args
|
||||
except (TypeError, ValueError, KeyError):
|
||||
bad = translation
|
||||
# fallback: apply to source before logging exception (in case source fails)
|
||||
translation = self._source % self._args
|
||||
_logger.exception('Bad translation %r for string %r', bad, self._source)
|
||||
return translation
|
||||
""" Translate."""
|
||||
return self._translate()
|
||||
|
||||
def __eq__(self, other):
|
||||
""" Prevent using equal operators
|
||||
|
|
@ -614,26 +676,50 @@ class _lt:
|
|||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def __hash__(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def __lt__(self, other):
|
||||
raise NotImplementedError()
|
||||
|
||||
def __add__(self, other):
|
||||
# Call _._get_translation() like _() does, so that we have the same number
|
||||
# of stack frames calling _get_translation()
|
||||
if isinstance(other, str):
|
||||
return _._get_translation(self._source) + other
|
||||
elif isinstance(other, _lt):
|
||||
return _._get_translation(self._source) + _._get_translation(other._source)
|
||||
return self._translate() + other
|
||||
elif isinstance(other, LazyGettext):
|
||||
return self._translate() + other._translate()
|
||||
return NotImplemented
|
||||
|
||||
def __radd__(self, other):
|
||||
# Call _._get_translation() like _() does, so that we have the same number
|
||||
# of stack frames calling _get_translation()
|
||||
if isinstance(other, str):
|
||||
return other + _._get_translation(self._source)
|
||||
return other + self._translate()
|
||||
return NotImplemented
|
||||
|
||||
_ = GettextAlias()
|
||||
|
||||
class LazyTranslate:
|
||||
""" Lazy translation template.
|
||||
|
||||
Usage:
|
||||
```
|
||||
_lt = LazyTranslate(__name__)
|
||||
MYSTR = _lt('Translate X')
|
||||
```
|
||||
|
||||
You may specify a `default_lang` to fallback to a given language on error
|
||||
"""
|
||||
module: str
|
||||
default_lang: str
|
||||
|
||||
def __init__(self, module: str, *, default_lang: str = '') -> None:
|
||||
self.module = module = get_translated_module(module or 2)
|
||||
# set the default lang to en_US for lazy translations in the base module
|
||||
self.default_lang = default_lang or ('en_US' if module == 'base' else '')
|
||||
|
||||
def __call__(self, source: str, *args, **kwargs) -> LazyGettext:
|
||||
return LazyGettext(source, *args, **kwargs, _module=self.module, _default_lang=self.default_lang)
|
||||
|
||||
|
||||
_ = get_text_alias
|
||||
_lt = LazyGettext
|
||||
|
||||
|
||||
def quote(s):
|
||||
|
|
@ -795,9 +881,10 @@ def TranslationFileWriter(target, fileformat='po', lang=None):
|
|||
'.csv, .po, or .tgz (received .%s).') % fileformat)
|
||||
|
||||
|
||||
_writer = codecs.getwriter('utf-8')
|
||||
class CSVFileWriter:
|
||||
def __init__(self, target):
|
||||
self.writer = pycompat.csv_writer(target, dialect='UNIX')
|
||||
self.writer = csv.writer(_writer(target), dialect='UNIX')
|
||||
# write header first
|
||||
self.writer.writerow(("module","type","name","res_id","src","value","comments"))
|
||||
|
||||
|
|
@ -867,22 +954,20 @@ class PoFileWriter:
|
|||
entry.comment = "module%s: %s" % (plural, ', '.join(modules))
|
||||
if comments:
|
||||
entry.comment += "\n" + "\n".join(comments)
|
||||
|
||||
code = False
|
||||
for typy, name, res_id in tnrs:
|
||||
if typy == 'code':
|
||||
code = True
|
||||
res_id = 0
|
||||
if isinstance(res_id, int) or res_id.isdigit():
|
||||
# second term of occurrence must be a digit
|
||||
# occurrence line at 0 are discarded when rendered to string
|
||||
entry.occurrences.append((u"%s:%s" % (typy, name), str(res_id)))
|
||||
occurrences = OrderedSet()
|
||||
for type_, *ref in tnrs:
|
||||
if type_ == "code":
|
||||
fpath, lineno = ref
|
||||
name = f"code:{fpath}"
|
||||
# lineno is set to 0 to avoid creating diff in PO files every
|
||||
# time the code is moved around
|
||||
lineno = "0"
|
||||
else:
|
||||
entry.occurrences.append((u"%s:%s:%s" % (typy, name, res_id), ''))
|
||||
if code:
|
||||
# TODO 17.0: remove the flag python-format in all PO/POT files
|
||||
# The flag is used in a wrong way. It marks all code translations even for javascript translations.
|
||||
entry.flags.append("python-format")
|
||||
field_name, xmlid = ref
|
||||
name = f"{type_}:{field_name}:{xmlid}"
|
||||
lineno = None # no lineno for model/model_terms sources
|
||||
occurrences.add((name, lineno))
|
||||
entry.occurrences = list(occurrences)
|
||||
self.po.append(entry)
|
||||
|
||||
|
||||
|
|
@ -952,7 +1037,6 @@ def _extract_translatable_qweb_terms(element, callback):
|
|||
if isinstance(el, SKIPPED_ELEMENT_TYPES): continue
|
||||
if (el.tag.lower() not in SKIPPED_ELEMENTS
|
||||
and "t-js" not in el.attrib
|
||||
and not ("t-jquery" in el.attrib and "t-operation" not in el.attrib)
|
||||
and not (el.tag == 'attribute' and el.get('name') not in TRANSLATED_ATTRS)
|
||||
and el.get("t-translation", '').strip() != "off"):
|
||||
|
||||
|
|
@ -997,7 +1081,7 @@ def extract_formula_terms(formula):
|
|||
tokens = generate_tokens(io.StringIO(formula).readline)
|
||||
tokens = (token for token in tokens if token.type not in {NEWLINE, INDENT, DEDENT})
|
||||
for t1 in tokens:
|
||||
if not t1.string == '_t':
|
||||
if t1.string != '_t':
|
||||
continue
|
||||
t2 = next(tokens, None)
|
||||
if t2 and t2.string == '(':
|
||||
|
|
@ -1031,16 +1115,19 @@ def extract_spreadsheet_terms(fileobj, keywords, comment_tags, options):
|
|||
if markdown_link:
|
||||
terms.add(markdown_link[1])
|
||||
for figure in sheet['figures']:
|
||||
terms.add(figure['data']['title'])
|
||||
if 'baselineDescr' in figure['data']:
|
||||
terms.add(figure['data']['baselineDescr'])
|
||||
pivots = data.get('pivots', {}).values()
|
||||
lists = data.get('lists', {}).values()
|
||||
for data_source in itertools.chain(lists, pivots):
|
||||
if 'name' in data_source:
|
||||
terms.add(data_source['name'])
|
||||
for global_filter in data.get('globalFilters', []):
|
||||
terms.add(global_filter['label'])
|
||||
if figure['tag'] == 'chart':
|
||||
title = figure['data']['title']
|
||||
if isinstance(title, str):
|
||||
terms.add(title)
|
||||
elif 'text' in title:
|
||||
terms.add(title['text'])
|
||||
if 'axesDesign' in figure['data']:
|
||||
terms.update(
|
||||
axes.get('title', {}).get('text', '') for axes in figure['data']['axesDesign'].values()
|
||||
)
|
||||
if 'baselineDescr' in figure['data']:
|
||||
terms.add(figure['data']['baselineDescr'])
|
||||
terms.update(global_filter['label'] for global_filter in data.get('globalFilters', []))
|
||||
return (
|
||||
(0, None, term, [])
|
||||
for term in terms
|
||||
|
|
@ -1059,7 +1146,7 @@ class TranslationReader:
|
|||
|
||||
def __iter__(self):
|
||||
for module, source, name, res_id, ttype, comments, _record_id, value in self._to_translate:
|
||||
yield (module, ttype, name, res_id, source, encode(odoo.tools.ustr(value)), comments)
|
||||
yield (module, ttype, name, res_id, source, value, comments)
|
||||
|
||||
def _push_translation(self, module, ttype, name, res_id, source, comments=None, record_id=None, value=None):
|
||||
""" Insert a translation that will be used in the file generation
|
||||
|
|
@ -1310,7 +1397,7 @@ class TranslationModuleReader(TranslationReader):
|
|||
lineno, message, comments = extracted[:3]
|
||||
value = translations.get(message, '')
|
||||
self._push_translation(module, trans_type, display_path, lineno,
|
||||
encode(message), comments + extra_comments, value=value)
|
||||
message, comments + extra_comments, value=value)
|
||||
except Exception:
|
||||
_logger.exception("Failed to extract terms from %s", fabsolutepath)
|
||||
finally:
|
||||
|
|
@ -1321,7 +1408,7 @@ class TranslationModuleReader(TranslationReader):
|
|||
|
||||
This will include:
|
||||
- the python strings marked with _() or _lt()
|
||||
- the javascript strings marked with _t() or _lt() inside static/src/js/
|
||||
- the javascript strings marked with _t() inside static/src/js/
|
||||
- the strings inside Qweb files inside static/src/xml/
|
||||
- the spreadsheet data files
|
||||
"""
|
||||
|
|
@ -1348,7 +1435,7 @@ class TranslationModuleReader(TranslationReader):
|
|||
for fname in fnmatch.filter(files, '*.js'):
|
||||
self._babel_extract_terms(fname, path, root, 'javascript',
|
||||
extra_comments=[JAVASCRIPT_TRANSLATION_COMMENT],
|
||||
extract_keywords={'_t': None, '_lt': None})
|
||||
extract_keywords={'_t': None})
|
||||
# QWeb template files
|
||||
for fname in fnmatch.filter(files, '*.xml'):
|
||||
self._babel_extract_terms(fname, path, root, 'odoo.tools.translate:babel_extract_qweb',
|
||||
|
|
@ -1629,11 +1716,7 @@ def load_language(cr, lang):
|
|||
installer.lang_install()
|
||||
|
||||
|
||||
def get_po_paths(module_name: str, lang: str):
|
||||
return get_po_paths_env(module_name, lang)
|
||||
|
||||
|
||||
def get_po_paths_env(module_name: str, lang: str, env: odoo.api.Environment | None = None):
|
||||
def get_po_paths(module_name: str, lang: str, env: odoo.api.Environment | None = None):
|
||||
lang_base = lang.split('_', 1)[0]
|
||||
# Load the base as a fallback in case a translation is missing:
|
||||
po_names = [lang_base, lang]
|
||||
|
|
@ -1642,7 +1725,7 @@ def get_po_paths_env(module_name: str, lang: str, env: odoo.api.Environment | No
|
|||
po_names.insert(1, 'es_419')
|
||||
po_paths = (
|
||||
join(module_name, dir_, filename + '.po')
|
||||
for filename in po_names
|
||||
for filename in OrderedSet(po_names)
|
||||
for dir_ in ('i18n', 'i18n_extra')
|
||||
)
|
||||
for path in po_paths:
|
||||
|
|
@ -1690,25 +1773,19 @@ class CodeTranslations:
|
|||
|
||||
def _load_python_translations(self, module_name, lang):
|
||||
def filter_func(row):
|
||||
# In the pot files with new translations, a code translation should have either
|
||||
# PYTHON_TRANSLATION_COMMENT or JAVASCRIPT_TRANSLATION_COMMENT for comments.
|
||||
# If a comment has neither the above comments, the pot file uses the deprecated
|
||||
# comments. And all code translations are stored as python translations.
|
||||
return row.get('value') and (
|
||||
PYTHON_TRANSLATION_COMMENT in row['comments']
|
||||
or JAVASCRIPT_TRANSLATION_COMMENT not in row['comments'])
|
||||
return row.get('value') and PYTHON_TRANSLATION_COMMENT in row['comments']
|
||||
translations = CodeTranslations._get_code_translations(module_name, lang, filter_func)
|
||||
self.python_translations[(module_name, lang)] = translations
|
||||
self.python_translations[(module_name, lang)] = ReadonlyDict(translations)
|
||||
|
||||
def _load_web_translations(self, module_name, lang):
|
||||
def filter_func(row):
|
||||
return row.get('value') and (
|
||||
JAVASCRIPT_TRANSLATION_COMMENT in row['comments']
|
||||
or WEB_TRANSLATION_COMMENT in row['comments'])
|
||||
return row.get('value') and JAVASCRIPT_TRANSLATION_COMMENT in row['comments']
|
||||
translations = CodeTranslations._get_code_translations(module_name, lang, filter_func)
|
||||
self.web_translations[(module_name, lang)] = {
|
||||
"messages": [{"id": src, "string": value} for src, value in translations.items()]
|
||||
}
|
||||
self.web_translations[(module_name, lang)] = ReadonlyDict({
|
||||
"messages": tuple(
|
||||
ReadonlyDict({"id": src, "string": value})
|
||||
for src, value in translations.items())
|
||||
})
|
||||
|
||||
def get_python_translations(self, module_name, lang):
|
||||
if (module_name, lang) not in self.python_translations:
|
||||
|
|
@ -1730,7 +1807,8 @@ def _get_translation_upgrade_queries(cr, field):
|
|||
field's column, while the queries in ``cleanup_queries`` remove the corresponding data from
|
||||
table ``_ir_translation``.
|
||||
"""
|
||||
Model = odoo.registry(cr.dbname)[field.model_name]
|
||||
from odoo.modules.registry import Registry # noqa: PLC0415
|
||||
Model = Registry(cr.dbname)[field.model_name]
|
||||
translation_name = f"{field.model_name},{field.name}"
|
||||
migrate_queries = []
|
||||
cleanup_queries = []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue