-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extracting translatable strings from manifests is easy enough to do in pure Python and removes an dependency on node_modules. The differences in the Python implementation versus the JavaScript version: - no longer relies on global state to gather the msgid's - doesn't encode duplicate filenames for the same msgid
- Loading branch information
Showing
3 changed files
with
116 additions
and
181 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
#!/usr/bin/python3 | ||
# This file is part of Cockpit. | ||
# | ||
# Copyright (C) 2025 Red Hat, Inc. | ||
# | ||
# This program is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation, either version 3 of the License, or | ||
# (at your option) any later version. | ||
# | ||
# This program is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
import argparse | ||
import collections | ||
import json | ||
import pathlib | ||
import re | ||
from typing import Any, DefaultDict, Dict, Iterable, List, Set | ||
|
||
PO_HEADER = """msgid "" | ||
msgstr "" | ||
"Project-Id-Version: PACKAGE_VERSION\\n" | ||
"MIME-Version: 1.0\\n" | ||
"Content-Type: text/plain; charset=UTF-8\\n" | ||
"Content-Transfer-Encoding: 8bit\\n" | ||
"X-Generator: Cockpit manifest2po\\n" | ||
""" | ||
|
||
|
||
def get_docs_strings(docs: List[Dict[str, str]]) -> Iterable[str]: | ||
for doc in docs: | ||
assert 'label' in doc, 'doc entry without label' | ||
yield doc['label'] | ||
|
||
|
||
def get_keyword_strings(keywords: List[Dict[str, List[str]]]) -> Iterable[str]: | ||
for keyword in keywords: | ||
assert 'matches' in keyword, 'keywords entry without matches' | ||
for match in keyword['matches']: | ||
yield match | ||
|
||
|
||
def get_menu_strings(menu: Dict[str, Any]) -> Iterable[str]: | ||
for _, entry in menu.items(): | ||
if 'label' in entry: | ||
yield entry['label'] | ||
if 'keywords' in entry: | ||
yield from get_keyword_strings(entry['keywords']) | ||
if 'docs' in entry: | ||
yield from get_docs_strings(entry['docs']) | ||
|
||
|
||
def get_bridges_strings(bridges: List[Dict[str, str]]) -> Iterable[str]: | ||
for bridge in bridges: | ||
if 'label' in bridge: | ||
yield bridge['label'] | ||
|
||
|
||
def get_manifest_strings(manifest: Dict[str, Any]) -> Iterable[str]: | ||
if 'menu' in manifest: | ||
yield from get_menu_strings(manifest['menu']) | ||
if 'tools' in manifest: | ||
yield from get_menu_strings(manifest['tools']) | ||
if 'bridges' in manifest: | ||
yield from get_bridges_strings(manifest['bridges']) | ||
|
||
|
||
def main() -> None: | ||
parser = argparse.ArgumentParser(prog='manifest2po', | ||
description='Extracts translatable strings from manifest.json files') | ||
parser.add_argument('-d', '--directory', help='Base directory for input files') | ||
parser.add_argument('-o', '--output', help='Output files', required=True) | ||
parser.add_argument('files', nargs='+', help='One or more input files', type=pathlib.Path, metavar='FILE') | ||
|
||
args = parser.parse_args() | ||
strings: DefaultDict[str, Set[str]] = collections.defaultdict(set) | ||
|
||
file: pathlib.Path | ||
for file in args.files: | ||
if file.name != 'manifest.json': | ||
continue | ||
|
||
# Qualify the filename if necessary | ||
full_path = args.directory / file if args.directory else file | ||
with open(full_path, 'r') as fp: | ||
data = fp.read() | ||
|
||
# There are variables which when not substituted can cause JSON.parse to fail | ||
# Dummy replace them. None variable is going to be translated anyway | ||
safe_data = re.sub(r"@.+?@", "1", data) | ||
manifest = json.loads(safe_data) | ||
for msgid in get_manifest_strings(manifest): | ||
strings[msgid].add(str(file)) | ||
|
||
# Write PO file | ||
with open(args.output, "w") as fp: | ||
fp.write(PO_HEADER) | ||
for msgid in strings: | ||
manifest_filenames = ' '.join([f'{fname}:0' for fname in strings[msgid]]) | ||
fp.write(f""" | ||
#: {manifest_filenames} | ||
msgid "{msgid}" | ||
msgstr "" | ||
""") | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters