odoo-toolkit 3.1.0__tar.gz → 3.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odoo_toolkit-3.1.0/README.md → odoo_toolkit-3.2.0/PKG-INFO +44 -0
- odoo_toolkit-3.1.0/PKG-INFO → odoo_toolkit-3.2.0/README.md +30 -14
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/dev/start.py +1 -1
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/multiverse_config/ruff.toml +4 -3
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/export.py +9 -12
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/common.py +127 -2
- odoo_toolkit-3.2.0/odoo_toolkit/wl/config.py +357 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/pyproject.toml +1 -1
- odoo_toolkit-3.1.0/odoo_toolkit/wl/config.py +0 -156
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/.gitignore +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/.vscode/settings.json +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/LICENSE +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/images/mailpit.png +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/__init__.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/__main__.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/common.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/dev/__init__.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/dev/common.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/dev/start_db.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/dev/stop.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/compose.yaml +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/.bash_aliases +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/17.0.Dockerfile +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/18.0.Dockerfile +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/18.4.Dockerfile +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/19.0.Dockerfile +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/append.bashrc +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/master.Dockerfile +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/starship.toml +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/startup.sh +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/pgadmin/servers.json +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/main.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/multiverse_config/.vscode/extensions.json +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/multiverse_config/.vscode/launch.json +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/multiverse_config/.vscode/settings.json +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/multiverse_config/requirements.txt +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/mv/__init__.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/mv/common.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/mv/reset.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/mv/setup.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/mv/switch.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/__init__.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/cldr-common-47/LICENSE +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/cldr-common-47/common/dtd/ldmlSupplemental.dtd +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/cldr-common-47/common/supplemental/plurals.xml +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/cldr.csv +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/common.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/create.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/update.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/__init__.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/autotranslate.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/copy.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/download.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/update_components.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/update_teams.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/upload.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/tests/__init__.py +0 -0
- {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/uv.lock +0 -0
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: odoo-toolkit
|
|
3
|
+
Version: 3.2.0
|
|
4
|
+
Summary: This toolkit contains a few useful tools for Odoo development.
|
|
5
|
+
Author-email: Dylan Kiss <dyki@odoo.com>
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: ~=3.10
|
|
8
|
+
Requires-Dist: gitpython>=3.1.46
|
|
9
|
+
Requires-Dist: polib>=1.2.0
|
|
10
|
+
Requires-Dist: python-on-whales>=0.76.1
|
|
11
|
+
Requires-Dist: requests>=2.32.4
|
|
12
|
+
Requires-Dist: typer>=0.15.4
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
1
15
|
# 🧰 Odoo Toolkit
|
|
2
16
|
|
|
3
17
|
This toolkit contains a few useful tools for Odoo development. The tools are primarily aimed at Odoo employees.
|
|
@@ -63,6 +77,7 @@ $ otk [OPTIONS] COMMAND [ARGS]...
|
|
|
63
77
|
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
64
78
|
| [`otk wl autotranslate`](#otk-wl-autotranslate) | Autotranslate translations for components in a Weblate project. |
|
|
65
79
|
| [`otk wl config`](#otk-wl-config) | Update modules in the Weblate config file. |
|
|
80
|
+
| [`otk wl config-odoo`](#otk-wl-config-odoo) | Generate the standard Odoo and Odoo l10n Weblate config profiles for a version. |
|
|
66
81
|
| [`otk wl copy`](#otk-wl-copy) | Copy translations between languages, components, and/or projects. |
|
|
67
82
|
| [`otk wl download`](#otk-wl-download) | Download specific PO files from Weblate. |
|
|
68
83
|
| [`otk wl update-components`](#otk-wl-update-components) | Update Weblate components based on the `.weblate.json` configuration in the current folder. |
|
|
@@ -467,6 +482,35 @@ $ otk wl config l10n_be -p odoo-18-l10n -l nl,fr,de
|
|
|
467
482
|
* `--help`: Show this message and exit.
|
|
468
483
|
|
|
469
484
|
|
|
485
|
+
## `otk wl config-odoo`
|
|
486
|
+
|
|
487
|
+
**Generate the standard Odoo and Odoo l10n Weblate config profiles for a version.**
|
|
488
|
+
|
|
489
|
+
This is a shorthand for generating both the main Odoo Weblate project and the matching l10n project with the default exclude lists used internally.
|
|
490
|
+
|
|
491
|
+
If you do not provide an Odoo version explicitly, the command tries to detect it from the Community path (or one of its parent folders) by looking for a folder named like `18.0`, `saas-18.2`, or `master`.
|
|
492
|
+
|
|
493
|
+
### Usage
|
|
494
|
+
|
|
495
|
+
```console
|
|
496
|
+
$ otk wl config-odoo [OPTIONS]
|
|
497
|
+
```
|
|
498
|
+
e.g.
|
|
499
|
+
|
|
500
|
+
```console
|
|
501
|
+
$ otk wl config-odoo
|
|
502
|
+
$ otk wl config-odoo -v saas-18.2
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Options
|
|
506
|
+
|
|
507
|
+
* `-v, --odoo-version TEXT`: The Odoo branch/version to configure, like `18.0`, `18`, `saas-18.2`, `18.2`, `s18-2`, or `master`. If omitted, it is detected from the current directory path.
|
|
508
|
+
* `-c, --com-path PATH`: Specify the path to your Odoo Community repository. [default: `odoo`]
|
|
509
|
+
* `-e, --ent-path PATH`: Specify the path to your Odoo Enterprise repository. [default: `enterprise`]
|
|
510
|
+
* `-a, --addons-path PATH`: Specify extra addons paths if your modules are not in Community or Enterprise.
|
|
511
|
+
* `--help`: Show this message and exit.
|
|
512
|
+
|
|
513
|
+
|
|
470
514
|
## `otk wl copy`
|
|
471
515
|
|
|
472
516
|
**Copy translations between languages, components, and/or projects.**
|
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: odoo-toolkit
|
|
3
|
-
Version: 3.1.0
|
|
4
|
-
Summary: This toolkit contains a few useful tools for Odoo development.
|
|
5
|
-
Author-email: Dylan Kiss <dyki@odoo.com>
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Requires-Python: ~=3.10
|
|
8
|
-
Requires-Dist: gitpython>=3.1.46
|
|
9
|
-
Requires-Dist: polib>=1.2.0
|
|
10
|
-
Requires-Dist: python-on-whales>=0.76.1
|
|
11
|
-
Requires-Dist: requests>=2.32.4
|
|
12
|
-
Requires-Dist: typer>=0.15.4
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
|
|
15
1
|
# 🧰 Odoo Toolkit
|
|
16
2
|
|
|
17
3
|
This toolkit contains a few useful tools for Odoo development. The tools are primarily aimed at Odoo employees.
|
|
@@ -77,6 +63,7 @@ $ otk [OPTIONS] COMMAND [ARGS]...
|
|
|
77
63
|
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
78
64
|
| [`otk wl autotranslate`](#otk-wl-autotranslate) | Autotranslate translations for components in a Weblate project. |
|
|
79
65
|
| [`otk wl config`](#otk-wl-config) | Update modules in the Weblate config file. |
|
|
66
|
+
| [`otk wl config-odoo`](#otk-wl-config-odoo) | Generate the standard Odoo and Odoo l10n Weblate config profiles for a version. |
|
|
80
67
|
| [`otk wl copy`](#otk-wl-copy) | Copy translations between languages, components, and/or projects. |
|
|
81
68
|
| [`otk wl download`](#otk-wl-download) | Download specific PO files from Weblate. |
|
|
82
69
|
| [`otk wl update-components`](#otk-wl-update-components) | Update Weblate components based on the `.weblate.json` configuration in the current folder. |
|
|
@@ -481,6 +468,35 @@ $ otk wl config l10n_be -p odoo-18-l10n -l nl,fr,de
|
|
|
481
468
|
* `--help`: Show this message and exit.
|
|
482
469
|
|
|
483
470
|
|
|
471
|
+
## `otk wl config-odoo`
|
|
472
|
+
|
|
473
|
+
**Generate the standard Odoo and Odoo l10n Weblate config profiles for a version.**
|
|
474
|
+
|
|
475
|
+
This is a shorthand for generating both the main Odoo Weblate project and the matching l10n project with the default exclude lists used internally.
|
|
476
|
+
|
|
477
|
+
If you do not provide an Odoo version explicitly, the command tries to detect it from the Community path (or one of its parent folders) by looking for a folder named like `18.0`, `saas-18.2`, or `master`.
|
|
478
|
+
|
|
479
|
+
### Usage
|
|
480
|
+
|
|
481
|
+
```console
|
|
482
|
+
$ otk wl config-odoo [OPTIONS]
|
|
483
|
+
```
|
|
484
|
+
e.g.
|
|
485
|
+
|
|
486
|
+
```console
|
|
487
|
+
$ otk wl config-odoo
|
|
488
|
+
$ otk wl config-odoo -v saas-18.2
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Options
|
|
492
|
+
|
|
493
|
+
* `-v, --odoo-version TEXT`: The Odoo branch/version to configure, like `18.0`, `18`, `saas-18.2`, `18.2`, `s18-2`, or `master`. If omitted, it is detected from the current directory path.
|
|
494
|
+
* `-c, --com-path PATH`: Specify the path to your Odoo Community repository. [default: `odoo`]
|
|
495
|
+
* `-e, --ent-path PATH`: Specify the path to your Odoo Enterprise repository. [default: `enterprise`]
|
|
496
|
+
* `-a, --addons-path PATH`: Specify extra addons paths if your modules are not in Community or Enterprise.
|
|
497
|
+
* `--help`: Show this message and exit.
|
|
498
|
+
|
|
499
|
+
|
|
484
500
|
## `otk wl copy`
|
|
485
501
|
|
|
486
502
|
**Copy translations between languages, components, and/or projects.**
|
|
@@ -181,7 +181,7 @@ def _get_odoo_container_version_from_branch(odoo_branch: str) -> str:
|
|
|
181
181
|
"""Get the Odoo version to use for the container based on the provided Odoo branch."""
|
|
182
182
|
if odoo_branch in ("master", "main"):
|
|
183
183
|
return "master"
|
|
184
|
-
match = re.search(r"(\d
|
|
184
|
+
match = re.search(r"(\d+(\.\d+)?)", odoo_branch)
|
|
185
185
|
if match:
|
|
186
186
|
version = float(match.group(1))
|
|
187
187
|
if version <= 17.0: # noqa: PLR2004
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
line-length = 120
|
|
2
|
-
target-version = "
|
|
2
|
+
target-version = "py312"
|
|
3
3
|
|
|
4
4
|
[lint]
|
|
5
5
|
preview = true
|
|
6
|
+
external = ["OLS"]
|
|
6
7
|
select = [
|
|
7
8
|
"BLE", # flake8-blind-except
|
|
8
9
|
"C", # flake8-comprehensions
|
|
@@ -54,6 +55,7 @@ ignore = [
|
|
|
54
55
|
"RUF001", # ambiguous-unicode-character-string
|
|
55
56
|
"RUF005", # collection-literal-concatenation
|
|
56
57
|
"RUF012", # mutable-class-default
|
|
58
|
+
"RUF067", # non-empty-init-module
|
|
57
59
|
"RUF100", # unused-noqa
|
|
58
60
|
"SIM102", # collapsible-if
|
|
59
61
|
"SIM108", # if-else-block-instead-of-if-exp
|
|
@@ -63,7 +65,6 @@ ignore = [
|
|
|
63
65
|
"TRY300", # try-consider-else
|
|
64
66
|
"TRY400", # error-instead-of-exception
|
|
65
67
|
"UP031", # printf-string-formatting
|
|
66
|
-
"UP038", # non-pep604-isinstance
|
|
67
68
|
]
|
|
68
69
|
|
|
69
70
|
[lint.per-file-ignores]
|
|
@@ -72,7 +73,7 @@ ignore = [
|
|
|
72
73
|
]
|
|
73
74
|
|
|
74
75
|
[lint.isort]
|
|
75
|
-
# https://www.odoo.com/documentation/
|
|
76
|
+
# https://www.odoo.com/documentation/latest/contributing/development/coding_guidelines.html#imports
|
|
76
77
|
section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"]
|
|
77
78
|
known-first-party = ["odoo"]
|
|
78
79
|
known-local-folder = ["odoo.addons"]
|
|
@@ -45,9 +45,8 @@ app = Typer()
|
|
|
45
45
|
|
|
46
46
|
class _ServerType(str, Enum):
|
|
47
47
|
COM = "Community"
|
|
48
|
-
COM_L10N = "Community Localizations"
|
|
49
48
|
ENT = "Enterprise"
|
|
50
|
-
|
|
49
|
+
L10N = "Localizations"
|
|
51
50
|
CUSTOM = "Custom"
|
|
52
51
|
|
|
53
52
|
|
|
@@ -291,7 +290,7 @@ def export(
|
|
|
291
290
|
|
|
292
291
|
if server_type == _ServerType.CUSTOM:
|
|
293
292
|
addons_path = [*extra_modules_paths, ent_modules_path, com_modules_path]
|
|
294
|
-
elif server_type in (_ServerType.ENT, _ServerType.
|
|
293
|
+
elif server_type in (_ServerType.ENT, _ServerType.L10N):
|
|
295
294
|
addons_path = [ent_modules_path, com_modules_path]
|
|
296
295
|
else:
|
|
297
296
|
addons_path = [com_modules_path]
|
|
@@ -701,23 +700,22 @@ def _get_modules_per_server_type(
|
|
|
701
700
|
modules_to_install: dict[_ServerType, set[str]] = defaultdict(set[str])
|
|
702
701
|
paths_and_filter_per_server_type: dict[_ServerType, tuple[list[Path], Callable[[str], bool] | None]] = {
|
|
703
702
|
_ServerType.COM: ([com_modules_path], lambda m: _is_exportable(m) and not is_l10n_module(m)),
|
|
704
|
-
_ServerType.COM_L10N: ([com_modules_path], _is_exportable),
|
|
705
703
|
_ServerType.ENT: (
|
|
706
704
|
[com_modules_path, ent_modules_path],
|
|
707
705
|
lambda m: _is_exportable(m) and not is_l10n_module(m),
|
|
708
706
|
),
|
|
709
|
-
_ServerType.
|
|
707
|
+
_ServerType.L10N: ([com_modules_path, ent_modules_path], _is_exportable),
|
|
710
708
|
_ServerType.CUSTOM: (extra_modules_paths, None),
|
|
711
709
|
}
|
|
712
710
|
|
|
713
711
|
# Determine all modules to export per server type.
|
|
714
712
|
for m, p in module_to_path.items():
|
|
715
713
|
if p.is_relative_to(com_modules_path):
|
|
716
|
-
modules_to_export[_ServerType.
|
|
717
|
-
modules_to_install[_ServerType.
|
|
714
|
+
modules_to_export[_ServerType.L10N if is_l10n_module(m) else _ServerType.COM].add(m)
|
|
715
|
+
modules_to_install[_ServerType.L10N if is_l10n_module(m) else _ServerType.COM].add(m)
|
|
718
716
|
elif p.is_relative_to(ent_modules_path):
|
|
719
|
-
modules_to_export[_ServerType.
|
|
720
|
-
modules_to_install[_ServerType.
|
|
717
|
+
modules_to_export[_ServerType.L10N if is_l10n_module(m) else _ServerType.ENT].add(m)
|
|
718
|
+
modules_to_install[_ServerType.L10N if is_l10n_module(m) else _ServerType.ENT].add(m)
|
|
721
719
|
elif any(p.is_relative_to(emp) for emp in extra_modules_paths) or m == "base":
|
|
722
720
|
# We want to export base with all addons paths, so we can get all module definitions in there.
|
|
723
721
|
modules_to_export[_ServerType.CUSTOM].add(m)
|
|
@@ -844,8 +842,7 @@ def _get_full_install_modules_per_server_type(
|
|
|
844
842
|
continue
|
|
845
843
|
# Add each Community module to the right server types.
|
|
846
844
|
if is_l10n_module(m):
|
|
847
|
-
modules[_ServerType.
|
|
848
|
-
modules[_ServerType.ENT_L10N].add(m)
|
|
845
|
+
modules[_ServerType.L10N].add(m)
|
|
849
846
|
else:
|
|
850
847
|
modules[_ServerType.COM].add(m)
|
|
851
848
|
modules[_ServerType.ENT].add(m)
|
|
@@ -857,7 +854,7 @@ def _get_full_install_modules_per_server_type(
|
|
|
857
854
|
continue
|
|
858
855
|
# Add each Enterprise module to the right server types.
|
|
859
856
|
if is_l10n_module(m):
|
|
860
|
-
modules[_ServerType.
|
|
857
|
+
modules[_ServerType.L10N].add(m)
|
|
861
858
|
else:
|
|
862
859
|
modules[_ServerType.ENT].add(m)
|
|
863
860
|
modules[_ServerType.CUSTOM].add(m)
|
|
@@ -2,6 +2,7 @@ import json
|
|
|
2
2
|
from collections import defaultdict
|
|
3
3
|
from collections.abc import Generator, Mapping
|
|
4
4
|
from enum import Enum
|
|
5
|
+
from fnmatch import fnmatch
|
|
5
6
|
from os import environ
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Any, Literal, TypedDict, TypeVar
|
|
@@ -87,6 +88,122 @@ class WeblateComponentData(TypedDict, total=False):
|
|
|
87
88
|
|
|
88
89
|
WeblateConfigType = dict[str, dict[str, list[WeblateComponentData]]]
|
|
89
90
|
|
|
91
|
+
# Maps fnmatch glob patterns (matched against module names) to a fixed list of language codes.
|
|
92
|
+
# Patterns are evaluated in order; the first match wins.
|
|
93
|
+
# Modules not listed here fall back to scanning available .po files.
|
|
94
|
+
L10N_LANGUAGE_MAP: dict[str, list[str] | None] = {
|
|
95
|
+
"*l10n_account_withholding_tax*": ["es_419", "id", "th"],
|
|
96
|
+
"*l10n_ae*": ["ar"],
|
|
97
|
+
"*l10n_ar*": ["es_419"],
|
|
98
|
+
"*l10n_at*": ["de"],
|
|
99
|
+
"*l10n_bd*": ["bn"],
|
|
100
|
+
"*l10n_be*": ["de", "fr", "nl"],
|
|
101
|
+
"*l10n_bf*": ["fr"],
|
|
102
|
+
"*l10n_bg*": ["bg"],
|
|
103
|
+
"*l10n_bh*": ["ar"],
|
|
104
|
+
"*l10n_bj*": ["fr"],
|
|
105
|
+
"*l10n_bo*": ["es_419"],
|
|
106
|
+
"*l10n_br*": ["pt_BR"],
|
|
107
|
+
"*l10n_ca*": ["fr"],
|
|
108
|
+
"*l10n_cd*": ["fr"],
|
|
109
|
+
"*l10n_cf*": ["fr"],
|
|
110
|
+
"*l10n_cg*": ["fr"],
|
|
111
|
+
"*l10n_ch*": ["de", "fr", "it"],
|
|
112
|
+
"*l10n_ci*": ["fr"],
|
|
113
|
+
"*l10n_cl*": ["es_419"],
|
|
114
|
+
"*l10n_cm*": ["fr"],
|
|
115
|
+
"*l10n_cn_city*": None,
|
|
116
|
+
"*l10n_cn*": ["zh_CN"],
|
|
117
|
+
"*l10n_co*": ["es_419"],
|
|
118
|
+
"*l10n_cr*": ["es_419"],
|
|
119
|
+
"*l10n_cy*": ["el", "tr"],
|
|
120
|
+
"*l10n_cz*": ["cs"],
|
|
121
|
+
"*l10n_de*": ["de"],
|
|
122
|
+
"*l10n_din5008*": ["de", "fr", "it"],
|
|
123
|
+
"*l10n_dk*": ["da"],
|
|
124
|
+
"*l10n_do*": ["es_419"],
|
|
125
|
+
"*l10n_dz*": ["ar", "fr"],
|
|
126
|
+
"*l10n_ec*": ["es_419"],
|
|
127
|
+
"*l10n_ee*": ["et"],
|
|
128
|
+
"*l10n_eg*": ["ar"],
|
|
129
|
+
"*l10n_employment_hero*": ["ms"],
|
|
130
|
+
"*l10n_es*": ["es"],
|
|
131
|
+
"*l10n_et*": ["am"],
|
|
132
|
+
"*l10n_eu*": ["bg", "cs", "da", "de", "el", "es", "et", "fi", "fr", "hr", "hu", "it", "lt", "lv", "nl", "pl", "pt", "ro", "sk", "sl", "sv"],
|
|
133
|
+
"*l10n_fi*": ["fi"],
|
|
134
|
+
"*l10n_fr*": ["fr"],
|
|
135
|
+
"*l10n_ga*": ["fr"],
|
|
136
|
+
"*l10n_gcc*": ["ar"],
|
|
137
|
+
"*l10n_gn*": ["fr"],
|
|
138
|
+
"*l10n_gq*": ["es", "fr", "pt"],
|
|
139
|
+
"*l10n_gr*": ["el"],
|
|
140
|
+
"*l10n_gt*": ["es_419"],
|
|
141
|
+
"*l10n_gw*": ["pt"],
|
|
142
|
+
"*l10n_hk*": ["zh_TW"],
|
|
143
|
+
"*l10n_hn*": ["es_419"],
|
|
144
|
+
"*l10n_hr*": ["hr"],
|
|
145
|
+
"*l10n_hu*": ["hu"],
|
|
146
|
+
"*l10n_id*": ["id"],
|
|
147
|
+
"*l10n_il*": ["he"],
|
|
148
|
+
"*l10n_in*": ["hi"],
|
|
149
|
+
"*l10n_iq*": ["ar"],
|
|
150
|
+
"*l10n_it*": ["it"],
|
|
151
|
+
"*l10n_jo*": ["ar"],
|
|
152
|
+
"*l10n_jp*": ["ja"],
|
|
153
|
+
"*l10n_ke*": ["sw"],
|
|
154
|
+
"*l10n_kh*": ["km"],
|
|
155
|
+
"*l10n_km*": ["ar", "fr"],
|
|
156
|
+
"*l10n_kr*": ["ko"],
|
|
157
|
+
"*l10n_kw*": ["ar"],
|
|
158
|
+
"*l10n_kz*": ["ru"],
|
|
159
|
+
"*l10n_latam*": ["es_419", "pt_BR"],
|
|
160
|
+
"*l10n_lb*": ["ar", "fr"],
|
|
161
|
+
"*l10n_lt*": ["lt"],
|
|
162
|
+
"*l10n_lu*": ["de", "fr", "lb"],
|
|
163
|
+
"*l10n_lv*": ["lv"],
|
|
164
|
+
"*l10n_ma*": ["ar", "fr"],
|
|
165
|
+
"*l10n_mc*": ["fr"],
|
|
166
|
+
"*l10n_ml*": ["fr"],
|
|
167
|
+
"*l10n_mr*": ["fr"],
|
|
168
|
+
"*l10n_mn*": ["mn"],
|
|
169
|
+
"*l10n_mu*": ["fr"],
|
|
170
|
+
"*l10n_mx*": ["es_419"],
|
|
171
|
+
"*l10n_my*": ["ms"],
|
|
172
|
+
"*l10n_mz*": ["pt"],
|
|
173
|
+
"*l10n_ne*": ["fr"],
|
|
174
|
+
"*l10n_nl*": ["nl"],
|
|
175
|
+
"*l10n_no*": ["nb"],
|
|
176
|
+
"*l10n_om*": ["ar"],
|
|
177
|
+
"*l10n_pa*": ["es_419"],
|
|
178
|
+
"*l10n_pe*": ["es_419"],
|
|
179
|
+
"*l10n_ph*": ["tl"],
|
|
180
|
+
"*l10n_pl*": ["pl"],
|
|
181
|
+
"*l10n_pt*": ["pt"],
|
|
182
|
+
"*l10n_qa*": ["ar"],
|
|
183
|
+
"*l10n_ro*": ["ro"],
|
|
184
|
+
"*l10n_rs*": ["sr@latin"],
|
|
185
|
+
"*l10n_rw*": ["fr", "sw"],
|
|
186
|
+
"*l10n_sa*": ["ar"],
|
|
187
|
+
"*l10n_se*": ["sv"],
|
|
188
|
+
"*l10n_si*": ["sl"],
|
|
189
|
+
"*l10n_sk*": ["sk"],
|
|
190
|
+
"*l10n_sn*": ["fr"],
|
|
191
|
+
"*l10n_syscohada*": ["fr"],
|
|
192
|
+
"*l10n_td*": ["ar", "fr"],
|
|
193
|
+
"*l10n_tg*": ["fr"],
|
|
194
|
+
"*l10n_th*": ["th"],
|
|
195
|
+
"*l10n_tn*": ["ar", "fr"],
|
|
196
|
+
"*l10n_tr*": ["tr"],
|
|
197
|
+
"*l10n_tw*": ["zh_TW"],
|
|
198
|
+
"*l10n_tz*": ["sw"],
|
|
199
|
+
"*l10n_ua*": ["uk"],
|
|
200
|
+
"*l10n_ug*": ["sw"],
|
|
201
|
+
"*l10n_uz*": ["uz"],
|
|
202
|
+
"*l10n_uy*": ["es_419"],
|
|
203
|
+
"*l10n_ve*": ["es_419"],
|
|
204
|
+
"*l10n_vn*": ["vi"],
|
|
205
|
+
}
|
|
206
|
+
|
|
90
207
|
|
|
91
208
|
class WeblateGroupResponse(TypedDict):
|
|
92
209
|
"""Minimal response structure of a Weblate group."""
|
|
@@ -382,8 +499,16 @@ class WeblateConfig:
|
|
|
382
499
|
"new_base": f"{relative_module_path}/i18n/{module_name}.pot",
|
|
383
500
|
}
|
|
384
501
|
if not languages and "l10n_" in module_name:
|
|
385
|
-
# If we are adding a l10n module,
|
|
386
|
-
|
|
502
|
+
# If we are adding a l10n module, check the fixed language map first.
|
|
503
|
+
fixed_language_match = next(
|
|
504
|
+
((pattern, langs) for pattern, langs in L10N_LANGUAGE_MAP.items() if fnmatch(module_name, pattern)),
|
|
505
|
+
None,
|
|
506
|
+
)
|
|
507
|
+
if fixed_language_match is None:
|
|
508
|
+
languages = sorted([lang.stem for lang in (module_path / "i18n").glob("*.po") if lang.is_file()])
|
|
509
|
+
else:
|
|
510
|
+
_, fixed_languages = fixed_language_match
|
|
511
|
+
languages = fixed_languages or []
|
|
387
512
|
if languages:
|
|
388
513
|
module_config["language_regex"] = f"^({'|'.join(sorted(languages))})$"
|
|
389
514
|
if existing_module_config:
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from fnmatch import fnmatch
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
from typer import Argument, Option, Typer
|
|
8
|
+
|
|
9
|
+
from odoo_toolkit.common import (
|
|
10
|
+
EMPTY_LIST,
|
|
11
|
+
TransientProgress,
|
|
12
|
+
get_valid_modules_to_path_mapping,
|
|
13
|
+
normalize_list_option,
|
|
14
|
+
print,
|
|
15
|
+
print_command_title,
|
|
16
|
+
print_error,
|
|
17
|
+
print_header,
|
|
18
|
+
print_success,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from .common import WeblateConfig, WeblateConfigError
|
|
22
|
+
|
|
23
|
+
app = Typer()
|
|
24
|
+
|
|
25
|
+
_DEFAULT_ODOO_CONFIG_EXCLUDES = ["*l10n_*", "hw_*", "*test*", "api_doc", "pos_blackbox_be", "iot_*"]
|
|
26
|
+
_DEFAULT_ODOO_L10N_EXCLUDES = [
|
|
27
|
+
"*test*",
|
|
28
|
+
"l10n_account_customer_statements",
|
|
29
|
+
"l10n_account_withholding_tax_pos",
|
|
30
|
+
"*l10n_anz*",
|
|
31
|
+
"*l10n_au*",
|
|
32
|
+
"l10n_cn_city",
|
|
33
|
+
"*l10n_gcc*",
|
|
34
|
+
"*l10n_ie*",
|
|
35
|
+
"*l10n_lk*",
|
|
36
|
+
"*l10n_mt*",
|
|
37
|
+
"*l10n_ng*",
|
|
38
|
+
"*l10n_nz*",
|
|
39
|
+
"*l10n_pk*",
|
|
40
|
+
"*l10n_sg*",
|
|
41
|
+
"*l10n_uk*",
|
|
42
|
+
"*l10n_za*",
|
|
43
|
+
"*l10n_zm*",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
_DEFAULT_ODOO_PROFILE_CONFIGS = [
|
|
47
|
+
("odoo-{version}", ["all"], _DEFAULT_ODOO_CONFIG_EXCLUDES),
|
|
48
|
+
("odoo-{version}-l10n", ["*l10n_*"], _DEFAULT_ODOO_L10N_EXCLUDES),
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _get_config_languages(module_path: Path, module_name: str, languages: list[str], lang_filter: bool) -> list[str] | None:
|
|
53
|
+
"""Resolve the language list for a module.
|
|
54
|
+
|
|
55
|
+
Returning None means the module should be skipped.
|
|
56
|
+
"""
|
|
57
|
+
if not lang_filter:
|
|
58
|
+
return languages
|
|
59
|
+
|
|
60
|
+
i18n_folder = module_path / "i18n"
|
|
61
|
+
module_languages = [po.stem for po in i18n_folder.glob("*.po")] if i18n_folder.is_dir() else EMPTY_LIST
|
|
62
|
+
if not module_languages and (i18n_folder / f"{module_name}.pot").is_file():
|
|
63
|
+
return None
|
|
64
|
+
return module_languages
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _save_config(weblate_config: WeblateConfig, updated: int, skipped: int) -> None:
|
|
68
|
+
"""Persist a Weblate config and report the outcome."""
|
|
69
|
+
try:
|
|
70
|
+
weblate_config.save()
|
|
71
|
+
print(f"Modules added or updated: [b]{updated}[/b]")
|
|
72
|
+
print(f"Modules skipped or removed: [b]{skipped}[/b]")
|
|
73
|
+
print_success("Config file successfully updated.\n")
|
|
74
|
+
except WeblateConfigError as e:
|
|
75
|
+
print_error("Config file update failed.\n", str(e))
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _update_addons_config(
|
|
79
|
+
weblate_config_path: Path,
|
|
80
|
+
local_modules: list[str],
|
|
81
|
+
module_to_path: dict[str, Path],
|
|
82
|
+
project: str,
|
|
83
|
+
languages: list[str],
|
|
84
|
+
lang_filter: bool,
|
|
85
|
+
reset: bool,
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Update one .weblate.json file for the given modules."""
|
|
88
|
+
print_header(f"Updating [u]{weblate_config_path}[/u]")
|
|
89
|
+
|
|
90
|
+
weblate_config = WeblateConfig(weblate_config_path)
|
|
91
|
+
if reset:
|
|
92
|
+
weblate_config.clear(project)
|
|
93
|
+
|
|
94
|
+
updated, skipped = 0, 0
|
|
95
|
+
for module_name in TransientProgress().track(local_modules, description="Updating modules ..."):
|
|
96
|
+
module_languages = _get_config_languages(module_to_path[module_name], module_name, languages, lang_filter)
|
|
97
|
+
if module_languages is None:
|
|
98
|
+
skipped += 1
|
|
99
|
+
continue
|
|
100
|
+
if weblate_config.update_module(module_to_path[module_name], project, normalize_list_option(module_languages)):
|
|
101
|
+
updated += 1
|
|
102
|
+
else:
|
|
103
|
+
skipped += 1
|
|
104
|
+
|
|
105
|
+
_save_config(weblate_config, updated, skipped)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _configure_odoo_profile(
|
|
109
|
+
project: str,
|
|
110
|
+
modules: list[str],
|
|
111
|
+
exclude: list[str],
|
|
112
|
+
com_path: Path,
|
|
113
|
+
ent_path: Path,
|
|
114
|
+
extra_addons_paths: list[Path],
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Apply one predefined Odoo Weblate config profile."""
|
|
117
|
+
print_header(f"Configuring [u]{project}[/u]")
|
|
118
|
+
_update_config(
|
|
119
|
+
modules=modules,
|
|
120
|
+
project=project,
|
|
121
|
+
exclude=exclude,
|
|
122
|
+
path_filters=EMPTY_LIST,
|
|
123
|
+
languages=EMPTY_LIST,
|
|
124
|
+
reset=True,
|
|
125
|
+
com_path=com_path,
|
|
126
|
+
ent_path=ent_path,
|
|
127
|
+
extra_addons_paths=extra_addons_paths,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _normalize_odoo_version(value: str) -> str:
|
|
132
|
+
"""Normalize an Odoo version/branch name to the Weblate project suffix."""
|
|
133
|
+
normalized = value.strip()
|
|
134
|
+
if normalized == "master":
|
|
135
|
+
return normalized
|
|
136
|
+
|
|
137
|
+
normalized = re.sub(r"^saas-", "s", normalized)
|
|
138
|
+
normalized = normalized.replace(".", "-")
|
|
139
|
+
if re.fullmatch(r"\d{1,2}-\d", normalized):
|
|
140
|
+
normalized = f"s{normalized}"
|
|
141
|
+
if re.fullmatch(r"\d{1,2}-0", normalized):
|
|
142
|
+
normalized = normalized[:-2]
|
|
143
|
+
if not re.fullmatch(r"(?:\d{1,2}|s\d{1,2}-\d|master)", normalized):
|
|
144
|
+
msg = (
|
|
145
|
+
"Could not normalize the provided Odoo version. "
|
|
146
|
+
"Use values like `18.0`, `18`, `saas-18.2`, `18.2`, `s18-2`, or `master`."
|
|
147
|
+
)
|
|
148
|
+
raise ValueError(msg)
|
|
149
|
+
return normalized
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _detect_odoo_version_from_path(start_path: Path) -> str:
|
|
153
|
+
"""Detect the current Odoo version/branch name from the given path or one of its parents."""
|
|
154
|
+
for candidate in (start_path, *start_path.parents):
|
|
155
|
+
if re.fullmatch(r"(?:master|(?:saas-)?\d{1,2}\.\d)", candidate.name):
|
|
156
|
+
return candidate.name
|
|
157
|
+
msg = (
|
|
158
|
+
"Could not detect an Odoo version from the Community path. "
|
|
159
|
+
"Use a Community path inside a branch folder like `18.0` or `saas-18.2`, or provide `--odoo-version`."
|
|
160
|
+
)
|
|
161
|
+
raise ValueError(msg)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _update_config(
|
|
165
|
+
modules: list[str],
|
|
166
|
+
project: str,
|
|
167
|
+
exclude: list[str],
|
|
168
|
+
path_filters: list[Path],
|
|
169
|
+
languages: list[str],
|
|
170
|
+
reset: bool,
|
|
171
|
+
com_path: Path,
|
|
172
|
+
ent_path: Path,
|
|
173
|
+
extra_addons_paths: list[Path],
|
|
174
|
+
) -> None:
|
|
175
|
+
"""Update one Weblate config profile."""
|
|
176
|
+
languages = sorted(normalize_list_option(languages))
|
|
177
|
+
exclude = normalize_list_option(exclude)
|
|
178
|
+
lang_filter = False
|
|
179
|
+
if "filter" in languages:
|
|
180
|
+
languages = EMPTY_LIST
|
|
181
|
+
lang_filter = True
|
|
182
|
+
|
|
183
|
+
def filter_fn(p: Path) -> bool:
|
|
184
|
+
if exclude and any(fnmatch(p.name, e) for e in exclude):
|
|
185
|
+
return False
|
|
186
|
+
if path_filters:
|
|
187
|
+
return any(p.is_relative_to(fp.expanduser().resolve()) for fp in path_filters)
|
|
188
|
+
return True
|
|
189
|
+
|
|
190
|
+
module_to_path = get_valid_modules_to_path_mapping(
|
|
191
|
+
modules=normalize_list_option(modules),
|
|
192
|
+
com_path=com_path,
|
|
193
|
+
ent_path=ent_path,
|
|
194
|
+
extra_addons_paths=extra_addons_paths,
|
|
195
|
+
filter_fn=filter_fn,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
print(f"Modules to include: [b]{'[/b], [b]'.join(sorted(module_to_path.keys()))}[/b]\n")
|
|
199
|
+
|
|
200
|
+
all_addons_paths = [p.expanduser().resolve() for p in [com_path, ent_path, *extra_addons_paths]]
|
|
201
|
+
|
|
202
|
+
addons_path_to_modules: dict[Path, list[str]] = defaultdict(list[str])
|
|
203
|
+
for module, module_path in module_to_path.items():
|
|
204
|
+
addons_path = next((ap for ap in all_addons_paths if module_path.is_relative_to(ap)), None)
|
|
205
|
+
if addons_path:
|
|
206
|
+
addons_path_to_modules[addons_path].append(module)
|
|
207
|
+
|
|
208
|
+
for addons_path, local_modules in addons_path_to_modules.items():
|
|
209
|
+
_update_addons_config(
|
|
210
|
+
weblate_config_path=addons_path / ".weblate.json",
|
|
211
|
+
local_modules=local_modules,
|
|
212
|
+
module_to_path=module_to_path,
|
|
213
|
+
project=project,
|
|
214
|
+
languages=languages,
|
|
215
|
+
lang_filter=lang_filter,
|
|
216
|
+
reset=reset,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@app.command()
|
|
221
|
+
def config(
|
|
222
|
+
modules: Annotated[
|
|
223
|
+
list[str],
|
|
224
|
+
Argument(help="Include these Odoo modules in `.weblate.json`, or either `all`, `community`, or `enterprise`."),
|
|
225
|
+
],
|
|
226
|
+
project: Annotated[str, Option("--project", "-p", help="Specify the Weblate project slug.")],
|
|
227
|
+
exclude: Annotated[
|
|
228
|
+
list[str], Option("--exclude", "-x", help="Exclude these modules from being added or updated."),
|
|
229
|
+
] = EMPTY_LIST,
|
|
230
|
+
path_filters: Annotated[
|
|
231
|
+
list[Path],
|
|
232
|
+
Option("--path-filter", "-f", help="Only add or update modules within these paths."),
|
|
233
|
+
] = EMPTY_LIST,
|
|
234
|
+
languages: Annotated[
|
|
235
|
+
list[str],
|
|
236
|
+
Option(
|
|
237
|
+
"--language",
|
|
238
|
+
"-l",
|
|
239
|
+
help="Define specific language codes for this component. Mostly used for localizations. "
|
|
240
|
+
"If none are given, it follows the default languages on Weblate. "
|
|
241
|
+
"If you want the specific PO file languages added as a filter, use `filter`.",
|
|
242
|
+
),
|
|
243
|
+
] = EMPTY_LIST,
|
|
244
|
+
reset: Annotated[
|
|
245
|
+
bool, Option("--reset", "-r", help="Reset the config file for the given project and only add the given modules."),
|
|
246
|
+
] = False,
|
|
247
|
+
com_path: Annotated[
|
|
248
|
+
Path,
|
|
249
|
+
Option(
|
|
250
|
+
"--com-path",
|
|
251
|
+
"-c",
|
|
252
|
+
help="Specify the path to your Odoo Community repository.",
|
|
253
|
+
),
|
|
254
|
+
] = Path("odoo"),
|
|
255
|
+
ent_path: Annotated[
|
|
256
|
+
Path,
|
|
257
|
+
Option(
|
|
258
|
+
"--ent-path",
|
|
259
|
+
"-e",
|
|
260
|
+
help="Specify the path to your Odoo Enterprise repository.",
|
|
261
|
+
),
|
|
262
|
+
] = Path("enterprise"),
|
|
263
|
+
extra_addons_paths: Annotated[
|
|
264
|
+
list[Path],
|
|
265
|
+
Option(
|
|
266
|
+
"--addons-path",
|
|
267
|
+
"-a",
|
|
268
|
+
help="Specify extra addons paths if your modules are not in Community or Enterprise.",
|
|
269
|
+
),
|
|
270
|
+
] = EMPTY_LIST,
|
|
271
|
+
) -> None:
|
|
272
|
+
"""Update modules in the Weblate config file.
|
|
273
|
+
|
|
274
|
+
This command will add, update, or remove module entries in `.weblate.json` files. The `.weblate.json` files need to
|
|
275
|
+
be located at the provided addons paths' roots. If not, a new file will be created.\n
|
|
276
|
+
\n
|
|
277
|
+
If no `languages` are provided, and a localization module is added, we will automatically limit the languages to the
|
|
278
|
+
ones currently available in that localization module.\n
|
|
279
|
+
\n
|
|
280
|
+
For `odoo` and `enterprise`, the project slug follows the format `odoo-18` for major versions and `odoo-s18-1` for
|
|
281
|
+
SaaS versions. Other repos have their own project slugs. Check the Weblate URLs to find the right project slug.
|
|
282
|
+
"""
|
|
283
|
+
print_command_title(":memo: Odoo Weblate Config")
|
|
284
|
+
|
|
285
|
+
_update_config(
|
|
286
|
+
modules=modules,
|
|
287
|
+
project=project,
|
|
288
|
+
exclude=exclude,
|
|
289
|
+
path_filters=path_filters,
|
|
290
|
+
languages=languages,
|
|
291
|
+
reset=reset,
|
|
292
|
+
com_path=com_path,
|
|
293
|
+
ent_path=ent_path,
|
|
294
|
+
extra_addons_paths=extra_addons_paths,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
@app.command()
|
|
299
|
+
def config_odoo(
|
|
300
|
+
odoo_version: Annotated[
|
|
301
|
+
str | None,
|
|
302
|
+
Option(
|
|
303
|
+
"--odoo-version",
|
|
304
|
+
"-v",
|
|
305
|
+
help=(
|
|
306
|
+
"The Odoo branch/version to configure, like `18.0`, `18`, `saas-18.2`, `18.2`, `s18-2`, or `master`. "
|
|
307
|
+
"If omitted, it is detected from the Community path (or one of its parent folders)."
|
|
308
|
+
),
|
|
309
|
+
),
|
|
310
|
+
] = None,
|
|
311
|
+
com_path: Annotated[
|
|
312
|
+
Path,
|
|
313
|
+
Option(
|
|
314
|
+
"--com-path",
|
|
315
|
+
"-c",
|
|
316
|
+
help="Specify the path to your Odoo Community repository.",
|
|
317
|
+
),
|
|
318
|
+
] = Path("odoo"),
|
|
319
|
+
ent_path: Annotated[
|
|
320
|
+
Path,
|
|
321
|
+
Option(
|
|
322
|
+
"--ent-path",
|
|
323
|
+
"-e",
|
|
324
|
+
help="Specify the path to your Odoo Enterprise repository.",
|
|
325
|
+
),
|
|
326
|
+
] = Path("enterprise"),
|
|
327
|
+
extra_addons_paths: Annotated[
|
|
328
|
+
list[Path],
|
|
329
|
+
Option(
|
|
330
|
+
"--addons-path",
|
|
331
|
+
"-a",
|
|
332
|
+
help="Specify extra addons paths if your modules are not in Community or Enterprise.",
|
|
333
|
+
),
|
|
334
|
+
] = EMPTY_LIST,
|
|
335
|
+
) -> None:
|
|
336
|
+
"""Generate the standard Odoo and Odoo l10n Weblate config profiles."""
|
|
337
|
+
print_command_title(":memo: Odoo Weblate Config Profiles")
|
|
338
|
+
|
|
339
|
+
try:
|
|
340
|
+
raw_odoo_version = odoo_version or _detect_odoo_version_from_path(com_path.expanduser().resolve())
|
|
341
|
+
normalized_odoo_version = _normalize_odoo_version(raw_odoo_version)
|
|
342
|
+
except ValueError as e:
|
|
343
|
+
print_error(str(e))
|
|
344
|
+
return
|
|
345
|
+
|
|
346
|
+
print(f"Detected Odoo version: [b]{raw_odoo_version}[/b]")
|
|
347
|
+
print(f"Using Weblate project suffix: [b]{normalized_odoo_version}[/b]\n")
|
|
348
|
+
|
|
349
|
+
for project_template, modules, exclude in _DEFAULT_ODOO_PROFILE_CONFIGS:
|
|
350
|
+
_configure_odoo_profile(
|
|
351
|
+
project=project_template.format(version=normalized_odoo_version),
|
|
352
|
+
modules=modules,
|
|
353
|
+
exclude=exclude,
|
|
354
|
+
com_path=com_path,
|
|
355
|
+
ent_path=ent_path,
|
|
356
|
+
extra_addons_paths=extra_addons_paths,
|
|
357
|
+
)
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
from collections import defaultdict
|
|
2
|
-
from fnmatch import fnmatch
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import Annotated
|
|
5
|
-
|
|
6
|
-
from typer import Argument, Option, Typer
|
|
7
|
-
|
|
8
|
-
from odoo_toolkit.common import (
|
|
9
|
-
EMPTY_LIST,
|
|
10
|
-
TransientProgress,
|
|
11
|
-
get_valid_modules_to_path_mapping,
|
|
12
|
-
normalize_list_option,
|
|
13
|
-
print,
|
|
14
|
-
print_command_title,
|
|
15
|
-
print_error,
|
|
16
|
-
print_header,
|
|
17
|
-
print_success,
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
from .common import WeblateConfig, WeblateConfigError
|
|
21
|
-
|
|
22
|
-
app = Typer()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@app.command()
|
|
26
|
-
def config(
|
|
27
|
-
modules: Annotated[
|
|
28
|
-
list[str],
|
|
29
|
-
Argument(help="Include these Odoo modules in `.weblate.json`, or either `all`, `community`, or `enterprise`."),
|
|
30
|
-
],
|
|
31
|
-
project: Annotated[str, Option("--project", "-p", help="Specify the Weblate project slug.")],
|
|
32
|
-
exclude: Annotated[
|
|
33
|
-
list[str], Option("--exclude", "-x", help="Exclude these modules from being added or updated."),
|
|
34
|
-
] = EMPTY_LIST,
|
|
35
|
-
path_filters: Annotated[
|
|
36
|
-
list[Path],
|
|
37
|
-
Option("--path-filter", "-f", help="Only add or update modules within these paths."),
|
|
38
|
-
] = EMPTY_LIST,
|
|
39
|
-
languages: Annotated[
|
|
40
|
-
list[str],
|
|
41
|
-
Option(
|
|
42
|
-
"--language",
|
|
43
|
-
"-l",
|
|
44
|
-
help="Define specific language codes for this component. Mostly used for localizations. "
|
|
45
|
-
"If none are given, it follows the default languages on Weblate. "
|
|
46
|
-
"If you want the specific PO file languages added as a filter, use `filter`.",
|
|
47
|
-
),
|
|
48
|
-
] = EMPTY_LIST,
|
|
49
|
-
reset: Annotated[
|
|
50
|
-
bool, Option("--reset", "-r", help="Reset the config file for the given project and only add the given modules."),
|
|
51
|
-
] = False,
|
|
52
|
-
com_path: Annotated[
|
|
53
|
-
Path,
|
|
54
|
-
Option(
|
|
55
|
-
"--com-path",
|
|
56
|
-
"-c",
|
|
57
|
-
help="Specify the path to your Odoo Community repository.",
|
|
58
|
-
),
|
|
59
|
-
] = Path("odoo"),
|
|
60
|
-
ent_path: Annotated[
|
|
61
|
-
Path,
|
|
62
|
-
Option(
|
|
63
|
-
"--ent-path",
|
|
64
|
-
"-e",
|
|
65
|
-
help="Specify the path to your Odoo Enterprise repository.",
|
|
66
|
-
),
|
|
67
|
-
] = Path("enterprise"),
|
|
68
|
-
extra_addons_paths: Annotated[
|
|
69
|
-
list[Path],
|
|
70
|
-
Option(
|
|
71
|
-
"--addons-path",
|
|
72
|
-
"-a",
|
|
73
|
-
help="Specify extra addons paths if your modules are not in Community or Enterprise.",
|
|
74
|
-
),
|
|
75
|
-
] = EMPTY_LIST,
|
|
76
|
-
) -> None:
|
|
77
|
-
"""Update modules in the Weblate config file.
|
|
78
|
-
|
|
79
|
-
This command will add, update, or remove module entries in `.weblate.json` files. The `.weblate.json` files need to
|
|
80
|
-
be located at the provided addons paths' roots. If not, a new file will be created.\n
|
|
81
|
-
\n
|
|
82
|
-
If no `languages` are provided, and a localization module is added, we will automatically limit the languages to the
|
|
83
|
-
ones currently available in that localization module.\n
|
|
84
|
-
\n
|
|
85
|
-
For `odoo` and `enterprise`, the project slug follows the format `odoo-18` for major versions and `odoo-s18-1` for
|
|
86
|
-
SaaS versions. Other repos have their own project slugs. Check the Weblate URLs to find the right project slug.
|
|
87
|
-
"""
|
|
88
|
-
print_command_title(":memo: Odoo Weblate Config")
|
|
89
|
-
|
|
90
|
-
languages = sorted(normalize_list_option(languages))
|
|
91
|
-
exclude = normalize_list_option(exclude)
|
|
92
|
-
lang_filter = False
|
|
93
|
-
if "filter" in languages:
|
|
94
|
-
languages = EMPTY_LIST
|
|
95
|
-
lang_filter = True
|
|
96
|
-
|
|
97
|
-
def filter_fn(p: Path) -> bool:
|
|
98
|
-
if exclude and any(fnmatch(p.name, e) for e in exclude):
|
|
99
|
-
return False
|
|
100
|
-
if path_filters:
|
|
101
|
-
return any(p.is_relative_to(fp.expanduser().resolve()) for fp in path_filters)
|
|
102
|
-
return True
|
|
103
|
-
|
|
104
|
-
module_to_path = get_valid_modules_to_path_mapping(
|
|
105
|
-
modules=normalize_list_option(modules),
|
|
106
|
-
com_path=com_path,
|
|
107
|
-
ent_path=ent_path,
|
|
108
|
-
extra_addons_paths=extra_addons_paths,
|
|
109
|
-
filter_fn=filter_fn,
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
print(f"Modules to include: [b]{'[/b], [b]'.join(sorted(module_to_path.keys()))}[/b]\n")
|
|
113
|
-
|
|
114
|
-
# Combine all paths into one list.
|
|
115
|
-
all_addons_paths = [p.expanduser().resolve() for p in [com_path, ent_path, *extra_addons_paths]]
|
|
116
|
-
|
|
117
|
-
# Create a mapping from each addons path to the relevant containing modules.
|
|
118
|
-
addons_path_to_modules: dict[Path, list[str]] = defaultdict(list[str])
|
|
119
|
-
for module, module_path in module_to_path.items():
|
|
120
|
-
addons_path = next((ap for ap in all_addons_paths if module_path.is_relative_to(ap)), None)
|
|
121
|
-
if addons_path:
|
|
122
|
-
addons_path_to_modules[addons_path].append(module)
|
|
123
|
-
addons_path_to_modules = dict(addons_path_to_modules)
|
|
124
|
-
|
|
125
|
-
# For each addons path, add the given modules to the .weblate.json.
|
|
126
|
-
for addons_path, local_modules in addons_path_to_modules.items():
|
|
127
|
-
weblate_config_path = addons_path / ".weblate.json"
|
|
128
|
-
|
|
129
|
-
print_header(f"Updating [u]{weblate_config_path}[/u]")
|
|
130
|
-
|
|
131
|
-
weblate_config = WeblateConfig(weblate_config_path)
|
|
132
|
-
if reset:
|
|
133
|
-
weblate_config.clear(project)
|
|
134
|
-
updated, skipped = 0, 0
|
|
135
|
-
for m in TransientProgress().track(local_modules, description="Updating modules ..."):
|
|
136
|
-
if lang_filter:
|
|
137
|
-
i18n_folder = module_to_path[m] / "i18n"
|
|
138
|
-
if i18n_folder.is_dir():
|
|
139
|
-
languages = [po.stem for po in (module_to_path[m] / "i18n").glob("*.po")]
|
|
140
|
-
else:
|
|
141
|
-
languages = EMPTY_LIST
|
|
142
|
-
if not languages and (i18n_folder / f"{m}.pot").is_file():
|
|
143
|
-
skipped += 1
|
|
144
|
-
continue
|
|
145
|
-
if weblate_config.update_module(module_to_path[m], project, normalize_list_option(languages)):
|
|
146
|
-
updated += 1
|
|
147
|
-
else:
|
|
148
|
-
skipped += 1
|
|
149
|
-
|
|
150
|
-
try:
|
|
151
|
-
weblate_config.save()
|
|
152
|
-
print(f"Modules added or updated: [b]{updated}[/b]")
|
|
153
|
-
print(f"Modules skipped or removed: [b]{skipped}[/b]")
|
|
154
|
-
print_success("Config file successfully updated.\n")
|
|
155
|
-
except WeblateConfigError as e:
|
|
156
|
-
print_error("Config file update failed.\n", str(e))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/multiverse_config/.vscode/extensions.json
RENAMED
|
File without changes
|
{odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/multiverse_config/.vscode/launch.json
RENAMED
|
File without changes
|
{odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/multiverse_config/.vscode/settings.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|