2

Context:

I am developping a small module to automatically rename photographs in a directory according to their exif timestamp (goal: easily mixing pictures from different cameras or smartphones). It works smoothly either as a Python package or directly from the command line through a tiny wrapper using argparse.

And I have just had the (rather stupid) idea to localize it in non English language. Ok, gettext is my friend for all my own code, but when I came to agparse generated messages, I found myself on a sloppy ground...

Current research:

I have already found some resources on SO:

Both end in adding the relevant strings from argparse into a po/mo file and let the argparse module automatically use the translated strings because internally it uses the _(...) wrapper. So far, so good.

My problem:

I feel this more as a workaround than a clean and neat solution because:

  • I could not find a word advising it in official Python documentation
  • It looks like a work in progress: implemented by not documented, so some strings could change in a future Python release (or did I miss something?)

Current code:

parser = argparse.ArgumentParser(
    prog = prog,
    description="Rename pictures according to their exif timestamp")
parser.add_argument("-v", "--version", action="version",
                    version="%(prog)s " + __version__)
parser.add_argument("--folder", "-f", default = ".",
                    help = "folder containing files to rename")
parser.add_argument("-s", "--src_mask", default="DSCF*.jpg",
                    help = "pattern to select the files to rename")
parser.add_argument("-d", "--dst_mask", default="%Y%m%d_%H%M%S",
                    help = "format for the new file name")
parser.add_argument("-e", "--ext", default=".jpg", dest="ext_mask",
                    help = "extension for the new file name")
parser.add_argument("-r", "--ref_file", default="names.log",
                    help = "a file to remember the old names")
parser.add_argument("-D", "--debug", action="store_true",
                    help = "print a line per rename")
parser.add_argument("-X", "--dry_run", action="store_true", dest="dummy",
                    help = "process normally except no rename occurs")

# subcommands configuration (rename, back, merge)
subparser = parser.add_subparsers(dest='subcommand', help="sub-commands")
ren = subparser.add_parser("rename", help=
                           "rename files by using their exif timestamp")
ren.add_argument("files", nargs="*",
                  help = "files to process (default: src_mask)")
back = subparser.add_parser("back",
                            help="rename files back to their original name")
back.add_argument("files", nargs="*",
                   help = "files to process (default: content of ref_file)")
merge = subparser.add_parser("merge",
                             help="merge files from a different folder")
merge.add_argument("src_folder", metavar="folder",
                    help = "folder from where merge picture files")
merge.add_argument("files", nargs="*",
                  help = "files to process (default: src_mask)")

I know how to wrap my own strings with _(), and I could probably have acceptable translations for the usage and help messages, but there are plenty of error messages when the user gives a wrong syntax, and I would like to prevent English error messages in the middle of French speaking program...

Question:

Is there any guarantee that the implementation of strings in the argparse module will not change, or is there a more robust/portable way to provide translations for its messages?

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • I've tried to follow all the `argparse` bug/issues, and there's been very little discussion of its use of `gettext`. (I haven't followed other issues specifically related to `gettext`). `argparse` isn't changing much, in large part because developers don't want backward compatibility problems. So if there are changes in the future they most likely will be enhancements that won't break existing code. – hpaulj Nov 05 '18 at 17:03
  • @hpaulj: thank you for your comment. It is sad that the list of messages is not explicitely part of the public interface, but I'm afraid I shall have to accept it... – Serge Ballesta Nov 06 '18 at 06:40
  • The unittest file `test_argparse.py` originally just tested for errors or not, and in a few cases for Exception class. It's only in the latter part of the file that some tests look at the message (usually with some sort regex expression). Most errors intended for end users (as opposed to code writers like you) pass through `ArgparseError` and `parser.error`. Those errors will include the `usage`, and Action 'name' (dest) where relevant. But I've never tried to take an inventory of possible error messages. – hpaulj Nov 06 '18 at 06:52
  • @hpaulj: I am mainly concerned here by errors intended to end users. I have already read `argparse.py` and its *recent* (several years) changes (after your comment), and I shall soon to the same with its test file. The bad news is that there are proposed conversions even for full error messages (hard to be exhaustive here), but it should be possible to convert most simpler strings and setup a *catch all* by overriding `ArgParser.error`. I shall experiment a little further, and I could submit an answer to my own question if I do not get good ones. Anyway, thank you for your nice feed back. – Serge Ballesta Nov 06 '18 at 08:56

1 Answers1

0

After some more research and @hpaulj's great comments, I can confirm:

  • the localizable messages from argparse are upward compatible from 3.3 to current version (old messages were never changed but new messages were added for new features)
  • the above is not true before 3.3
  • there are slight differences in 2.7

That means that only 2 paths are possible here:

  • accept the risk and provide a translation for the current version that will accept any Python version >= 3.3 - the risk is that a future version breaks the translation or add new (untranslated) messages. Nothing more to say because this will explicitely use the implementation details of the module
  • do not use at all argparse module and build a custom parser based on getopt. It is probably an acceptable option for simple use cases that do not require the full power of argparse

None of them are really good, but I cannot imagine a better one...

I will try to setup a project on github or gitlab providing the pot file and the french translation for the current argparse, and make it available on PyPI. If it ever exists, I will add references for it here and shall be glad to include other languages there.


A beta version of a project giving French translations for the argparse module is currently available on GitHUB

I call it beta because at this time, it has not been extensively tested, but it can be used either directly or as an example of what could be done. The binary wheel contains a little endian mo file, but the source distribution allows the mo file to be generated automatically on the target system with no additional dependency by including a copy of the msgfmt.py file from the Tools i18n of CPython.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252