Skip to content

Instantly share code, notes, and snippets.

@jcfr
Last active March 30, 2023 20:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcfr/fc2bbb1e6b30d08c06f56d403f10145c to your computer and use it in GitHub Desktop.
Save jcfr/fc2bbb1e6b30d08c06f56d403f10145c to your computer and use it in GitHub Desktop.
Rewrite tr-like function calls found in Slicer scripted modules to include the context as first argument
# SPDX-FileCopyrightText: 2023 Jean-Christophe Fillion-Robin <jcfr@kitware.com>
# SPDX-License-Identifier: BSD-3-Clause
import ast
import argparse
import errno
import os
import sys
import astor
class TrFunctionRewriter(ast.NodeTransformer):
def __init__(self, input_file):
self.current_context = os.path.splitext(os.path.basename(input_file))[0]
"""Replace tr to QT_TRANSLATE_NOOP."""
def visit_Call(self, node):
self.generic_visit(node)
# Transform tr-like functions into 'QT_TRANSLATE_NOOP' """
if (isinstance(node.func, ast.Name) and \
(node.func.id in ["_", "tr", "translate"]) and \
len(node.args) == 1):
# Insert the context
updated_args = [ast.Constant(self.current_context)] + node.args
call = ast.Call(func=ast.Name(id=node.func.id, ctx=ast.Load()),
args=updated_args,
keywords=[])
ast.copy_location(call, node)
# Add lineno & col_offset to the nodes we created
ast.fix_missing_locations(call)
return call
# Return the original node if we don't want to change it.
return node
def mkdir_p(path):
"""Ensure directory ``path`` exists. If needed, parent directories
are created.
Adapted from http://stackoverflow.com/a/600612/1539918
"""
try:
os.makedirs(path)
except OSError as exc: # Python >2.5
if exc.errno == errno.EEXIST and os.path.isdir(path):
pass
else: # pragma: no cover
raise
def main(argv):
parser = argparse.ArgumentParser(description="slicer_lupdate_preprocess.py")
parser.add_argument("-i", "--ifile", type=str, help="input file")
parser.add_argument("-o", "--ofile", type=str, help="output file")
args = parser.parse_args()
if not args.ifile or not args.ofile:
parser.print_help()
sys.exit(2)
input_file = args.ifile
output_file = args.ofile
with open(input_file, "r") as source:
tree = ast.parse(source.read())
tree_new = TrFunctionRewriter(input_file=input_file).visit(tree)
all_lines = astor.to_source(tree_new)
# if needed, create output directory
output_dir = os.path.dirname(output_file)
if output_dir:
mkdir_p(output_dir)
with open(output_file, "w") as destination:
for line in all_lines:
destination.write(line)
print(f"output_file [{output_file}]")
if __name__ == "__main__":
main(sys.argv[1:])
@jcfr
Copy link
Author

jcfr commented Mar 30, 2023

This script was created in the context of pull-request Slicer#6917 and is based of prior work done in the context of pull-request Slicer#6050 from 2021.

Input: MyModule.py

from slicer.translation import tr as _


class MyModuleWidget:

    def someFunction(self):
        myMessage = _("Press any key to continue")
        myMessage = _("Press {key} to continue").format(key=key)

Usage

!pip install astor

python slicer_lupdate_preprocess.py -i MyModule.py -o MyModule.lupdate.py
diff MyModule.py MyModule.lupdate.py
7,8c7,8
<         myMessage = _("Press any key to continue")
<         myMessage = _("Press {key} to continue").format(key=key)
---
>         myMessage = _('MyModule', 'Press any key to continue')
>         myMessage = _('MyModule', 'Press {key} to continue').format(key=key)
!pip install pyside6

$ pyside6-lupdate MyModule.lupdate.py -ts MyModule.ts -tr-function-alias translate+=tr,translate+=_
Updating 'MyModule.ts'...
    Found 2 source text(s) (2 new and 0 already existing)
$ cat MyModule.ts
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
    <name>MyModule</name>
    <message>
        <location filename="MyModule.tr.py" line="8"/>
        <source>Press any key to continue</source>
        <translation type="unfinished"></translation>
    </message>
    <message>
        <location filename="MyModule.tr.py" line="8"/>
        <source>Press {key} to continue</source>
        <translation type="unfinished"></translation>
    </message>
</context>
</TS>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment