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.
Files changed (58) hide show
  1. odoo_toolkit-3.1.0/README.md → odoo_toolkit-3.2.0/PKG-INFO +44 -0
  2. odoo_toolkit-3.1.0/PKG-INFO → odoo_toolkit-3.2.0/README.md +30 -14
  3. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/dev/start.py +1 -1
  4. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/multiverse_config/ruff.toml +4 -3
  5. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/export.py +9 -12
  6. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/common.py +127 -2
  7. odoo_toolkit-3.2.0/odoo_toolkit/wl/config.py +357 -0
  8. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/pyproject.toml +1 -1
  9. odoo_toolkit-3.1.0/odoo_toolkit/wl/config.py +0 -156
  10. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/.gitignore +0 -0
  11. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/.vscode/settings.json +0 -0
  12. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/LICENSE +0 -0
  13. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/images/mailpit.png +0 -0
  14. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/__init__.py +0 -0
  15. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/__main__.py +0 -0
  16. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/common.py +0 -0
  17. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/dev/__init__.py +0 -0
  18. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/dev/common.py +0 -0
  19. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/dev/start_db.py +0 -0
  20. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/dev/stop.py +0 -0
  21. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/compose.yaml +0 -0
  22. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/.bash_aliases +0 -0
  23. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/17.0.Dockerfile +0 -0
  24. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/18.0.Dockerfile +0 -0
  25. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/18.4.Dockerfile +0 -0
  26. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/19.0.Dockerfile +0 -0
  27. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/append.bashrc +0 -0
  28. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/master.Dockerfile +0 -0
  29. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/starship.toml +0 -0
  30. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/odoo/startup.sh +0 -0
  31. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/docker/pgadmin/servers.json +0 -0
  32. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/main.py +0 -0
  33. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/multiverse_config/.vscode/extensions.json +0 -0
  34. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/multiverse_config/.vscode/launch.json +0 -0
  35. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/multiverse_config/.vscode/settings.json +0 -0
  36. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/multiverse_config/requirements.txt +0 -0
  37. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/mv/__init__.py +0 -0
  38. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/mv/common.py +0 -0
  39. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/mv/reset.py +0 -0
  40. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/mv/setup.py +0 -0
  41. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/mv/switch.py +0 -0
  42. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/__init__.py +0 -0
  43. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/cldr-common-47/LICENSE +0 -0
  44. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/cldr-common-47/common/dtd/ldmlSupplemental.dtd +0 -0
  45. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/cldr-common-47/common/supplemental/plurals.xml +0 -0
  46. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/cldr.csv +0 -0
  47. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/common.py +0 -0
  48. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/create.py +0 -0
  49. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/po/update.py +0 -0
  50. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/__init__.py +0 -0
  51. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/autotranslate.py +0 -0
  52. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/copy.py +0 -0
  53. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/download.py +0 -0
  54. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/update_components.py +0 -0
  55. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/update_teams.py +0 -0
  56. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/odoo_toolkit/wl/upload.py +0 -0
  57. {odoo_toolkit-3.1.0 → odoo_toolkit-3.2.0}/tests/__init__.py +0 -0
  58. {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+\.\d+)", odoo_branch)
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 = "py310"
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/master/contributing/development/coding_guidelines.html#imports
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
- ENT_L10N = "Enterprise Localizations"
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.ENT_L10N):
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.ENT_L10N: ([com_modules_path, ent_modules_path], _is_exportable),
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.COM_L10N if is_l10n_module(m) else _ServerType.COM].add(m)
717
- modules_to_install[_ServerType.COM_L10N if is_l10n_module(m) else _ServerType.COM].add(m)
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.ENT_L10N if is_l10n_module(m) else _ServerType.ENT].add(m)
720
- modules_to_install[_ServerType.ENT_L10N if is_l10n_module(m) else _ServerType.ENT].add(m)
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.COM_L10N].add(m)
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.ENT_L10N].add(m)
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, only add the available languages.
386
- languages = sorted([lang.stem for lang in (module_path / "i18n").glob("*.po") if lang.is_file()])
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,6 +1,6 @@
1
1
  [project]
2
2
  name = "odoo-toolkit"
3
- version = "3.1.0"
3
+ version = "3.2.0"
4
4
  description = "This toolkit contains a few useful tools for Odoo development."
5
5
  authors = [{ name = "Dylan Kiss", email = "dyki@odoo.com" }]
6
6
  requires-python = "~=3.10"
@@ -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