File: generate_po_files.py

package info (click to toggle)
python-holidays 0.86-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 57,296 kB
  • sloc: python: 117,830; javascript: 85; makefile: 59
file content (140 lines) | stat: -rwxr-xr-x 5,183 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#!/usr/bin/env python3


#  holidays
#  --------
#  A fast, efficient Python library for generating country, province and state
#  specific sets of holidays on the fly. It aims to make determining whether a
#  specific date is a holiday as fast and flexible as possible.
#
#  Authors: Vacanza Team and individual contributors (see CONTRIBUTORS file)
#           dr-prodigy <dr.prodigy.github@gmail.com> (c) 2017-2023
#           ryanss <ryanssdev@icloud.com> (c) 2014-2017
#  Website: https://github.com/vacanza/holidays
#  License: MIT (see LICENSE file)

import importlib
import inspect
import sys
from concurrent.futures import ProcessPoolExecutor
from pathlib import Path
from time import perf_counter

from lingva.extract import extract as create_pot_file
from lingva.extract import _location_sort_key
from polib import pofile

sys.path.insert(0, str(Path.cwd()))  # Make holidays visible.
from holidays import __version__ as package_version
from holidays.holiday_base import HolidayBase

WRAP_WIDTH = 99


class POGenerator:
    """Generates .po files for supported country/market entities."""

    @staticmethod
    def _process_entity_worker(
        entity_code_info: tuple[str, tuple[str, Path]],
    ) -> list[tuple[str, str]]:
        """Process a single entity: create .pot, default .po, and return update tasks."""
        entity_code, (default_language, class_file_path) = entity_code_info

        locale_path = Path("holidays/locale")
        pot_path = locale_path / "pot"
        pot_path.mkdir(exist_ok=True)

        pot_file_path = pot_path / f"{entity_code}.pot"

        # Create .pot file.
        create_pot_file(
            sources=[class_file_path],
            keywords=["tr"],
            output=pot_file_path,
            package_name="Holidays",
            package_version=package_version,
            width=WRAP_WIDTH,
            allow_empty=True,
        )

        # Update .pot file metadata.
        pot_file = pofile(pot_file_path, wrapwidth=WRAP_WIDTH)
        pot_file.metadata.update(
            {
                "Language": default_language,
                "Language-Team": "Holidays Localization Team",
                "X-Source-Language": default_language,
            }
        )
        pot_file.save(newline="\n")

        # Create entity default .po file from the .pot file.
        po_directory = locale_path / default_language / "LC_MESSAGES"
        po_directory.mkdir(parents=True, exist_ok=True)
        default_po_path = po_directory / f"{entity_code}.po"
        if not default_po_path.exists():
            pot_file.save(str(default_po_path), newline="\n")

        # Collect .po update tasks.
        return [
            (str(po_file_path), str(pot_file_path))
            for po_file_path in locale_path.rglob(f"{entity_code}.po")
        ]

    @staticmethod
    def _update_po_file(args: tuple[str, str]) -> None:
        """Merge .po file with .pot"""
        po_path, pot_path = args
        po_file = pofile(po_path, wrapwidth=WRAP_WIDTH)
        po_file_initial = po_file.copy()

        po_file.merge(pofile(pot_path))
        po_file.sort(key=_location_sort_key)
        for entry in po_file:
            entry.occurrences.clear()

        # Only update the project version if po file translation entries has changed.
        if po_file != po_file_initial:
            po_file.metadata["Project-Id-Version"] = f"Holidays {package_version}"

        # Save the file each time in order to capture all other changes properly.
        po_file.save(po_path, newline="\n")

    def process_entities(self):
        """Processes entities in specified directory."""
        entity_code_info_mapping = {}
        for entity_type in ("countries", "financial"):
            for path in Path(f"holidays/{entity_type}").glob("*.py"):
                if path.stem == "__init__":
                    continue
                module = f"holidays.{entity_type}.{path.stem}"
                for _, cls in inspect.getmembers(importlib.import_module(module), inspect.isclass):
                    if (
                        issubclass(cls, HolidayBase)
                        and cls.__module__ == module
                        and getattr(cls, "default_language") is not None
                    ):
                        name = getattr(cls, "country", getattr(cls, "market", None))
                        entity_code_info_mapping[name.upper()] = (cls.default_language, path)
                        break

        all_po_update_tasks: list[tuple[str, str]] = []
        with ProcessPoolExecutor() as executor:
            for po_tasks in executor.map(
                self._process_entity_worker, entity_code_info_mapping.items()
            ):
                all_po_update_tasks.extend(po_tasks)
            list(executor.map(POGenerator._update_po_file, all_po_update_tasks))

    @staticmethod
    def run():
        """Runs the .po files generation process."""
        POGenerator().process_entities()


if __name__ == "__main__":
    po_time_start = perf_counter()
    POGenerator.run()
    po_time_end = perf_counter()
    print(f"[TIMER] Total generate po files runtime: {po_time_end - po_time_start:.2f} seconds")