From 73b5ac88202e6560b01cf37c82f03d51996746a8 Mon Sep 17 00:00:00 2001 From: s-ball Date: Sun, 2 Dec 2018 19:51:51 +0100 Subject: [PATCH 01/36] Add test for current behaviour of msgfmt.py Test option processing, and conversion of one single file with or without the -o option. Also test the little documented behaviour of merging two input files with -o option. --- Lib/test/test_tools/test_i18n.py | 185 ++++++++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_tools/test_i18n.py b/Lib/test/test_tools/test_i18n.py index 8b2b90d6142bb4..df4c492bd35e46 100644 --- a/Lib/test/test_tools/test_i18n.py +++ b/Lib/test/test_tools/test_i18n.py @@ -5,9 +5,9 @@ import unittest from textwrap import dedent -from test.support.script_helper import assert_python_ok +from test.support.script_helper import assert_python_ok, assert_python_failure from test.test_tools import skip_if_missing, toolsdir -from test.support import temp_cwd, temp_dir +from test.support import temp_cwd, temp_dir, unload, change_cwd skip_if_missing() @@ -243,3 +243,184 @@ def test_files_list(self): self.assertIn(f'msgid "{text1}"', data) self.assertIn(f'msgid "{text2}"', data) self.assertNotIn(text3, data) + + +class Test_msgfmt(unittest.TestCase): + """Tests for the msgfmt.py tool + bpo-35335 - bpo-9741 + """ + + script = os.path.join(toolsdir,'i18n', 'msgfmt.py') + + # binary images of tiny po files + # windows end of lines for first one + file1_fr_po = b'''# French translations for python package.\r +# Copyright (C) 2018 THE python\'S COPYRIGHT HOLDER\r +# This file is distributed under the same license as the python package.\r +# s-ball , 2018.\r +#\r +msgid ""\r +msgstr ""\r +"Project-Id-Version: python 3.8\\n"\r +"Report-Msgid-Bugs-To: \\n"\r +"POT-Creation-Date: 2018-11-30 23:46+0100\\n"\r +"PO-Revision-Date: 2018-11-30 23:47+0100\\n"\r +"Last-Translator: s-ball \\n"\r +"Language-Team: French\\n"\r\n"Language: fr\\n"\r +"MIME-Version: 1.0\\n"\r +"Content-Type: text/plain; charset=UTF-8\\n"\r +"Content-Transfer-Encoding: 8bit\\n"\r +"Plural-Forms: nplurals=2; plural=(n > 1);\\n"\r +\r +#: file1.py:6\r +msgid "Hello!"\r +msgstr "Bonjour !"\r +\r +#: file1.py:7\r +#, python-brace-format\r +msgid "{n} horse"\r +msgid_plural "{n} horses"\r +msgstr[0] "{n} cheval"\r +msgstr[1] "{n} chevaux"\r +''' + + # Unix end of file and a non ascii character for second one + file2_fr_po = rb'''# French translations for python package. +# Copyright (C) 2018 THE python'S COPYRIGHT HOLDER +# This file is distributed under the same license as the python package. +# s-ball , 2018. +# +msgid "" +msgstr "" +"Project-Id-Version: python 3.8\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-11-30 23:57+0100\n" +"PO-Revision-Date: 2018-11-30 23:57+0100\n" +"Last-Translator: s-ball \n" +"Language-Team: French\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: file2.py:6 +msgid "It's over." +msgstr "C'est termin\xc3\xa9." + +#: file2.py:7 +msgid "Bye..." +msgstr "Au revoir ..." +''' + + # binary images of corresponding compiled mo files + file1_fr_mo = b'\xde\x12\x04\x95\x00\x00\x00\x00\x03\x00\x00\x00\x1c' \ + b'\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ + b'\x00\x00\x00\x00\x00L\x00\x00\x00\x06\x00\x00\x00M' \ + b'\x00\x00\x00\x14\x00\x00\x00T\x00\x00\x00[\x01\x00' \ + b'\x00i\x00\x00\x00\t\x00\x00\x00\xc5\x01\x00\x00\x16' \ + b'\x00\x00\x00\xcf\x01\x00\x00\x00Hello!\x00{n} horse' \ + b'\x00{n} horses\x00Project-Id-Version: python 3.8\n' \ + b'Report-Msgid-Bugs-To: \nPOT-Creation-Date: 2018-11-30 ' \ + b'23:46+0100\nPO-Revision-Date: 2018-11-30 23:47+0100\n'\ + b'Last-Translator: s-ball \n' \ + b'Language-Team: French\nLanguage: fr\nMIME-Version: 1.0' \ + b'\nContent-Type: text/plain; charset=UTF-8\nContent-' \ + b'Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2' \ + b'; plural=(n > 1);\n\x00Bonjour !\x00{n} cheval\x00{n' \ + b'} chevaux\x00' + file2_fr_mo = b"\xde\x12\x04\x95\x00\x00\x00\x00\x03\x00\x00\x00" \ + b"\x1c\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00" \ + b"\x00\x00\x00\x00\x00\x00\x00L\x00\x00\x00\x06\x00" \ + b"\x00\x00M\x00\x00\x00\n\x00\x00\x00T\x00\x00\x00[" \ + b"\x01\x00\x00_\x00\x00\x00\r\x00\x00\x00\xbb\x01" \ + b"\x00\x00\x0f\x00\x00\x00\xc9\x01\x00\x00\x00Bye.." \ + b".\x00It's over.\x00Project-Id-Version: python 3.8" \ + b"\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2018" \ + b"-11-30 23:57+0100\nPO-Revision-Date: 2018-11-30 2" \ + b"3:57+0100\nLast-Translator: s-ball \nLanguage-Team: French\nLanguage: fr\nMIME" \ + b"-Version: 1.0\nContent-Type: text/plain; charset=" \ + b"UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Fo" \ + b"rms: nplurals=2; plural=(n > 1);\n\x00Au revoir ." \ + b"..\x00C'est termin\xc3\xa9.\x00" + + # image of merging both po files keeping second header + file12_fr_mo = b"\xde\x12\x04\x95\x00\x00\x00\x00\x05\x00\x00\x00" \ + b"\x1c\x00\x00\x00D\x00\x00\x00\x00\x00\x00\x00\x00" \ + b"\x00\x00\x00\x00\x00\x00\x00l\x00\x00\x00\x06\x00" \ + b"\x00\x00m\x00\x00\x00\x06\x00\x00\x00t\x00\x00\x00" \ + b"\n\x00\x00\x00{\x00\x00\x00\x14\x00\x00\x00\x86" \ + b"\x00\x00\x00[\x01\x00\x00\x9b\x00\x00\x00\r\x00" \ + b"\x00\x00\xf7\x01\x00\x00\t\x00\x00\x00\x05\x02\x00" \ + b"\x00\x0f\x00\x00\x00\x0f\x02\x00\x00\x16\x00\x00" \ + b"\x00\x1f\x02\x00\x00\x00Bye...\x00Hello!\x00It's " \ + b"over.\x00{n} horse\x00{n} horses\x00Project-Id-Ver" \ + b"sion: python 3.8\nReport-Msgid-Bugs-To: \nPOT-Crea" \ + b"tion-Date: 2018-11-30 23:57+0100\nPO-Revision-Date" \ + b": 2018-11-30 23:57+0100\nLast-Translator: s-ball <" \ + b"s-ball@laposte.net>\nLanguage-Team: French\nLangua" \ + b"ge: fr\nMIME-Version: 1.0\nContent-Type: text/plai" \ + b"n; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ + b"\nPlural-Forms: nplurals=2; plural=(n > 1);\n\x00A" \ + b"u revoir ...\x00Bonjour !\x00C'est termin\xc3\xa9." \ + b"\x00{n} cheval\x00{n} chevaux\x00" + def imp(self): + i18ndir = os.path.join(toolsdir, 'i18n') + sys.path.append(i18ndir) + import msgfmt + sys.path.remove(i18ndir) + return msgfmt + + def test_help(self): + """Test option -h""" + rc, stdout, stderr = assert_python_ok(self.script, '-h') + self.assertEqual(0, rc) + self.assertTrue(stderr.startswith( + b'Generate binary message catalog from textual' + b' translation description.' + ) + ) + + def test_wrong(self): + """Test wrong option""" + rc, stdout, stderr = assert_python_failure(self.script, '-x') + self.assertEqual(1, rc) + self.assertTrue(stderr.startswith( + b'Generate binary message catalog from textual' + b' translation description.' + ) + ) + + def test_outputfile(self): + """Test script with -o option - 1 single file, Windows EOL""" + with temp_cwd(None): + with open("file1_fr.po", "wb") as out: + out.write(self.file1_fr_po) + assert_python_ok(self.script, '-o', 'file1.mo', 'file1_fr.po') + with open('file1.mo', 'rb') as fin: + self.assertEqual(self.file1_fr_mo, fin.read()) + + def test_no_outputfile(self): + """Test script without -o option - 1 single file, Unix EOL""" + with temp_cwd(None): + with open("file2_fr.po", "wb") as out: + out.write(self.file2_fr_po) + assert_python_ok(self.script, 'file2_fr.po') + with open('file2_fr.mo', 'rb') as fin: + self.assertEqual(self.file2_fr_mo, fin.read()) + + def test_both_with_outputfile(self): + """Test script with -o option and 2 input files""" + # msgfmt.py version 1.2 behaviour is to correctly merge the input + # files and to keep last entry when same entry occurs in more than + # one file + with temp_cwd(None): + with open("file1_fr.po", "wb") as out: + out.write(self.file1_fr_po) + with open("file2_fr.po", "wb") as out: + out.write(self.file2_fr_po) + assert_python_ok(self.script, '-o', 'file1.mo', + 'file1_fr.po', 'file2_fr.po') + with open('file1.mo', 'rb') as fin: + self.assertEqual(self.file12_fr_mo, fin.read()) From 5fb1575c1b812741a4c4f99787ec09bbd2f9893d Mon Sep 17 00:00:00 2001 From: s-ball Date: Sun, 2 Dec 2018 23:26:17 +0100 Subject: [PATCH 02/36] Final fix for bpo-9741, with tests proving it. Also show that it is now possible to build multiple po files in one single script call. --- Lib/test/test_tools/test_i18n.py | 32 ++++++++++++++ Tools/i18n/msgfmt.py | 72 +++++++++++++++++++------------- 2 files changed, 76 insertions(+), 28 deletions(-) diff --git a/Lib/test/test_tools/test_i18n.py b/Lib/test/test_tools/test_i18n.py index df4c492bd35e46..6f03173a064e74 100644 --- a/Lib/test/test_tools/test_i18n.py +++ b/Lib/test/test_tools/test_i18n.py @@ -424,3 +424,35 @@ def test_both_with_outputfile(self): 'file1_fr.po', 'file2_fr.po') with open('file1.mo', 'rb') as fin: self.assertEqual(self.file12_fr_mo, fin.read()) + + def test_both_without_outputfile(self): + """Test script without -o option and 2 input files""" + # msgfmt.py version 1.2 behaviour was that second mo file + # also merged previous po files + with temp_cwd(None): + with open("file1_fr.po", "wb") as out: + out.write(self.file1_fr_po) + with open("file2_fr.po", "wb") as out: + out.write(self.file2_fr_po) + assert_python_ok(self.script, 'file1_fr.po', 'file2_fr.po') + with open('file1_fr.mo', 'rb') as fin: + self.assertEqual(self.file1_fr_mo, fin.read()) + with open('file2_fr.mo', 'rb') as fin: + self.assertEqual(self.file2_fr_mo, fin.read()) + + def test_consecutive_make_calls(self): + """Directly calls make twice to prove bpo-9741 is fixed""" + sys.path.append(os.path.join(toolsdir,'i18n')) + from msgfmt import make + with temp_cwd(None): + with open("file1_fr.po", "wb") as out: + out.write(self.file1_fr_po) + with open("file2_fr.po", "wb") as out: + out.write(self.file2_fr_po) + make("file1_fr.po", "file1_fr.mo") + make("file2_fr.po", "file2_fr.mo") + with open('file1_fr.mo', 'rb') as fin: + self.assertEqual(self.file1_fr_mo, fin.read()) + with open('file2_fr.mo', 'rb') as fin: + self.assertEqual(self.file2_fr_mo, fin.read()) + sys.path.pop() diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py index 3f731e941eafe7..0073dc551dc47d 100755 --- a/Tools/i18n/msgfmt.py +++ b/Tools/i18n/msgfmt.py @@ -1,6 +1,6 @@ #! /usr/bin/env python3 # Written by Martin v. Löwis - +# Version 1.3 by s-ball """Generate binary message catalog from textual translation description. This program converts a textual Uniforum-style message catalog (.po file) into @@ -33,7 +33,7 @@ import array from email.parser import HeaderParser -__version__ = "1.2" +__version__ = "1.3" MESSAGES = {} @@ -45,29 +45,27 @@ def usage(code, msg=''): sys.exit(code) -def add(ctxt, id, str, fuzzy): +def add(ctxt, id, str, fuzzy, messages): "Add a non-fuzzy translation to the dictionary." - global MESSAGES if not fuzzy and str: if ctxt is None: - MESSAGES[id] = str + messages[id] = str else: - MESSAGES[b"%b\x04%b" % (ctxt, id)] = str + messages[b"%b\x04%b" % (ctxt, id)] = str -def generate(): +def generate(messages): "Return the generated output." - global MESSAGES # the keys are sorted in the .mo file - keys = sorted(MESSAGES.keys()) + keys = sorted(messages.keys()) offsets = [] ids = strs = b'' for id in keys: # For each string, we need size and file offset. Each string is NUL # terminated; the NUL does not count into the size. - offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id]))) + offsets.append((len(ids), len(id), len(strs), len(messages[id]))) ids += id + b'\0' - strs += MESSAGES[id] + b'\0' + strs += messages[id] + b'\0' output = '' # The header is 7 32-bit unsigned integers. We don't use hash tables, so # the keys start right after the index tables. @@ -96,11 +94,27 @@ def generate(): return output -def make(filename, outfile): - ID = 1 - STR = 2 - CTXT = 3 +def make(filenames, outfile): + messages = {} + if isinstance(filenames, str): + infile, outfile = get_names(filenames, outfile) + process(infile, messages) + elif outfile is None: + for filename in filenames: + infile, outfile = get_names(filename, None) + messages.clear() + process(infile, messages) + output = generate(messages) + writefile(outfile, output) + return + else: + for filename in filenames: + infile, _ = get_names(filename, outfile) + process(infile, messages) + output = generate(messages) + writefile(outfile, output) +def get_names(filename, outfile): # Compute .mo name from .po name and arguments if filename.endswith('.po'): infile = filename @@ -108,6 +122,12 @@ def make(filename, outfile): infile = filename + '.po' if outfile is None: outfile = os.path.splitext(infile)[0] + '.mo' + return infile, outfile + +def process(infile, messages): + ID = 1 + STR = 2 + CTXT = 3 try: with open(infile, 'rb') as f: @@ -130,7 +150,7 @@ def make(filename, outfile): lno += 1 # If we get a comment line after a msgstr, this is a new entry if l[0] == '#' and section == STR: - add(msgctxt, msgid, msgstr, fuzzy) + add(msgctxt, msgid, msgstr, fuzzy, messages) section = msgctxt = None fuzzy = 0 # Record a fuzzy mark @@ -142,13 +162,13 @@ def make(filename, outfile): # Now we are in a msgid or msgctxt section, output previous section if l.startswith('msgctxt'): if section == STR: - add(msgctxt, msgid, msgstr, fuzzy) + add(msgctxt, msgid, msgstr, fuzzy, messages) section = CTXT l = l[7:] msgctxt = b'' elif l.startswith('msgid') and not l.startswith('msgid_plural'): if section == STR: - add(msgctxt, msgid, msgstr, fuzzy) + add(msgctxt, msgid, msgstr, fuzzy, messages) if not msgid: # See whether there is an encoding declaration p = HeaderParser() @@ -203,11 +223,9 @@ def make(filename, outfile): sys.exit(1) # Add last entry if section == STR: - add(msgctxt, msgid, msgstr, fuzzy) - - # Compute output - output = generate() + add(msgctxt, msgid, msgstr, fuzzy, messages) +def writefile(outfile, output): try: with open(outfile,"wb") as f: f.write(output) @@ -215,9 +233,9 @@ def make(filename, outfile): print(msg, file=sys.stderr) -def main(): +def main(argv): try: - opts, args = getopt.getopt(sys.argv[1:], 'hVo:', + opts, args = getopt.getopt(argv, 'hVo:', ['help', 'version', 'output-file=']) except getopt.error as msg: usage(1, msg) @@ -237,10 +255,8 @@ def main(): print('No input file given', file=sys.stderr) print("Try `msgfmt --help' for more information.", file=sys.stderr) return - - for filename in args: - make(filename, outfile) + make(args, outfile) if __name__ == '__main__': - main() + main(sys.argv[1:]) From b1968e94f918e3d8621d7ea9b1da0788aafbf3a2 Mon Sep 17 00:00:00 2001 From: s-ball Date: Sun, 2 Dec 2018 23:49:51 +0100 Subject: [PATCH 03/36] Update docstrings for the script and make function --- Tools/i18n/msgfmt.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py index 0073dc551dc47d..6581c955b98d75 100755 --- a/Tools/i18n/msgfmt.py +++ b/Tools/i18n/msgfmt.py @@ -6,9 +6,9 @@ This program converts a textual Uniforum-style message catalog (.po file) into a binary GNU catalog (.mo file). This is essentially the same function as the GNU msgfmt program, however, it is a simpler implementation. Currently it -does not handle plural forms but it does handle message contexts. +handles plural forms and message contexts, but does not generate hash table. -Usage: msgfmt.py [OPTIONS] filename.po +Usage: msgfmt.py [OPTIONS] filename.po [filename.po ...] Options: -o file @@ -23,6 +23,14 @@ -V --version Display version information and exit. + +If more than one input file is given, and if an output file is passed with +-o option, then all the input files are merged. If keys are repeated (common +for "" key for the header) the one from last file is used. + +If more than one input file is given, and no -o option is present, then +every input file is compiled in its corresponding mo file (same name with mo +replacing po) """ import os @@ -95,6 +103,15 @@ def generate(messages): def make(filenames, outfile): + """This function is now member of the public interface. + filenames is a string or an iterable of strings representing input file(s) + outfile is a string for the name of an input file or None. + + If it is not None, the output file receives a merge of the input files + If it is None, then each input file is separately compiled into its + corresponding output file (same name with po replaced with mo). + Both ways are for compatibility reasons with previous behaviour. + """ messages = {} if isinstance(filenames, str): infile, outfile = get_names(filenames, outfile) From 0bc4ad356db68ac734696ae327da135567ac89e2 Mon Sep 17 00:00:00 2001 From: s-ball Date: Tue, 4 Dec 2018 10:42:23 +0100 Subject: [PATCH 04/36] Give make a simpler and more consistent interface. --- Tools/i18n/msgfmt.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py index 6581c955b98d75..8ce573f473bd62 100755 --- a/Tools/i18n/msgfmt.py +++ b/Tools/i18n/msgfmt.py @@ -108,8 +108,8 @@ def make(filenames, outfile): outfile is a string for the name of an input file or None. If it is not None, the output file receives a merge of the input files - If it is None, then each input file is separately compiled into its - corresponding output file (same name with po replaced with mo). + If it is None, then filenames must be a string and the name of the output + file is obtained by replacing the po extension with mo. Both ways are for compatibility reasons with previous behaviour. """ messages = {} @@ -117,13 +117,7 @@ def make(filenames, outfile): infile, outfile = get_names(filenames, outfile) process(infile, messages) elif outfile is None: - for filename in filenames: - infile, outfile = get_names(filename, None) - messages.clear() - process(infile, messages) - output = generate(messages) - writefile(outfile, output) - return + raise TypeError("outfile cannot be None with more than one infile") else: for filename in filenames: infile, _ = get_names(filename, outfile) @@ -272,7 +266,11 @@ def main(argv): print('No input file given', file=sys.stderr) print("Try `msgfmt --help' for more information.", file=sys.stderr) return - make(args, outfile) + if outfile is None: + for filename in args: + make(filename, None) + else: + make(args, outfile) if __name__ == '__main__': From a9e67b429705bd625915a44e552f60514cba4523 Mon Sep 17 00:00:00 2001 From: s-ball Date: Wed, 5 Dec 2018 20:46:45 +0100 Subject: [PATCH 05/36] Add a Misc/NEWS.d entry. --- .../next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst diff --git a/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst b/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst new file mode 100644 index 00000000000000..451536f3f1bc55 --- /dev/null +++ b/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst @@ -0,0 +1,3 @@ +It is now possible to merge more than one single po file into a compiled mo +file. When an entry exists in more than on input file, the last file wins. +It is also possible to directly call ``make`` more than once From 4390edeeacecf7fa9193e11dfb820c2b42e2dee3 Mon Sep 17 00:00:00 2001 From: s-ball Date: Thu, 23 Jan 2025 19:32:03 +0100 Subject: [PATCH 06/36] Fix an import error in test_i18n.py . When merging main into multi_inputs, the reference to os_helper was erroneously removed. --- Lib/test/test_tools/test_i18n.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_tools/test_i18n.py b/Lib/test/test_tools/test_i18n.py index a9c7458c8adec9..6d5fc7751ed85c 100644 --- a/Lib/test/test_tools/test_i18n.py +++ b/Lib/test/test_tools/test_i18n.py @@ -9,7 +9,7 @@ from test.support.script_helper import assert_python_ok, assert_python_failure from test.test_tools import skip_if_missing, toolsdir -from test.support import temp_cwd, temp_dir, unload, change_cwd +from test.support.os_helper import temp_cwd, temp_dir, unload, change_cwd skip_if_missing() From 008ea275aaf08609801ebafff7ccaaa49c0a31c9 Mon Sep 17 00:00:00 2001 From: s-ball Date: Thu, 23 Jan 2025 20:07:20 +0100 Subject: [PATCH 07/36] Fix another import error. In 2018, all imports came from the test.support package. They are now splitted among various subpackages. --- Lib/test/test_tools/test_i18n.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_tools/test_i18n.py b/Lib/test/test_tools/test_i18n.py index 6d5fc7751ed85c..195c80d8612199 100644 --- a/Lib/test/test_tools/test_i18n.py +++ b/Lib/test/test_tools/test_i18n.py @@ -9,7 +9,8 @@ from test.support.script_helper import assert_python_ok, assert_python_failure from test.test_tools import skip_if_missing, toolsdir -from test.support.os_helper import temp_cwd, temp_dir, unload, change_cwd +from test.support.os_helper import temp_cwd, temp_dir, change_cwd +from test.support.import_helper import unload skip_if_missing() From ba26b80a1d570b06ac428fa84dbcaa03d0e1f36f Mon Sep 17 00:00:00 2001 From: s-ball Date: Sun, 23 Feb 2025 23:36:26 +0100 Subject: [PATCH 08/36] Revert version number to 1.2 The newly added tests require a 1.2 version. --- Tools/i18n/msgfmt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py index be8e5ae17ec49c..59ebb6acfe67c3 100755 --- a/Tools/i18n/msgfmt.py +++ b/Tools/i18n/msgfmt.py @@ -1,6 +1,6 @@ #! /usr/bin/env python3 # Written by Martin v. Löwis -# Version 1.3 by s-ball +# Modified by s-ball """Generate binary message catalog from textual translation description. This program converts a textual Uniforum-style message catalog (.po file) into @@ -42,7 +42,7 @@ from email.parser import HeaderParser import codecs -__version__ = "1.3" +__version__ = "1.2" MESSAGES = {} From 1ecc1f3698b9ff984cb478779c0ffc6d156d20ed Mon Sep 17 00:00:00 2001 From: s-ball Date: Thu, 27 Feb 2025 17:42:18 +0100 Subject: [PATCH 09/36] Fix a merge error --- Lib/test/test_tools/test_i18n.py | 51 +++++++++++++------------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/Lib/test/test_tools/test_i18n.py b/Lib/test/test_tools/test_i18n.py index 104202ac274f46..ce04d13735d84a 100644 --- a/Lib/test/test_tools/test_i18n.py +++ b/Lib/test/test_tools/test_i18n.py @@ -7,16 +7,9 @@ from textwrap import dedent from pathlib import Path -<<<<<<< HEAD from test.support.script_helper import assert_python_ok, assert_python_failure -from test.test_tools import skip_if_missing, toolsdir -from test.support.os_helper import temp_cwd, temp_dir, change_cwd -from test.support.import_helper import unload -======= -from test.support.script_helper import assert_python_ok from test.test_tools import imports_under_tool, skip_if_missing, toolsdir from test.support.os_helper import temp_cwd, temp_dir ->>>>>>> main skip_if_missing() @@ -505,7 +498,25 @@ def test_parse_keyword_spec(self): with self.subTest(spec=spec): self.assertEqual(parse_spec(spec), expected) -<<<<<<< HEAD + invalid = ( + ('foo:', "Invalid keyword spec 'foo:': missing argument positions"), + ('foo:bar', "Invalid keyword spec 'foo:bar': position is not an integer"), + ('foo:0', "Invalid keyword spec 'foo:0': argument positions must be strictly positive"), + ('foo:-2', "Invalid keyword spec 'foo:-2': argument positions must be strictly positive"), + ('foo:1,1', "Invalid keyword spec 'foo:1,1': duplicate positions"), + ('foo:1,2,1', "Invalid keyword spec 'foo:1,2,1': duplicate positions"), + ('foo:1c,2,1c', "Invalid keyword spec 'foo:1c,2,1c': duplicate positions"), + ('foo:1c,2,3c', "Invalid keyword spec 'foo:1c,2,3c': msgctxt can only appear once"), + ('foo:1,2,3', "Invalid keyword spec 'foo:1,2,3': too many positions"), + ('foo:1c', "Invalid keyword spec 'foo:1c': msgctxt cannot appear without msgid"), + ) + for spec, message in invalid: + with self.subTest(spec=spec): + with self.assertRaises(ValueError) as cm: + parse_spec(spec) + self.assertEqual(str(cm.exception), message) + + class Test_msgfmt(unittest.TestCase): """Tests for the msgfmt.py tool bpo-35335 - bpo-9741 @@ -719,28 +730,6 @@ def test_consecutive_make_calls(self): sys.path.pop() -def update_POT_snapshots(): - for input_file in DATA_DIR.glob('*.py'): -======= - invalid = ( - ('foo:', "Invalid keyword spec 'foo:': missing argument positions"), - ('foo:bar', "Invalid keyword spec 'foo:bar': position is not an integer"), - ('foo:0', "Invalid keyword spec 'foo:0': argument positions must be strictly positive"), - ('foo:-2', "Invalid keyword spec 'foo:-2': argument positions must be strictly positive"), - ('foo:1,1', "Invalid keyword spec 'foo:1,1': duplicate positions"), - ('foo:1,2,1', "Invalid keyword spec 'foo:1,2,1': duplicate positions"), - ('foo:1c,2,1c', "Invalid keyword spec 'foo:1c,2,1c': duplicate positions"), - ('foo:1c,2,3c', "Invalid keyword spec 'foo:1c,2,3c': msgctxt can only appear once"), - ('foo:1,2,3', "Invalid keyword spec 'foo:1,2,3': too many positions"), - ('foo:1c', "Invalid keyword spec 'foo:1c': msgctxt cannot appear without msgid"), - ) - for spec, message in invalid: - with self.subTest(spec=spec): - with self.assertRaises(ValueError) as cm: - parse_spec(spec) - self.assertEqual(str(cm.exception), message) - - def extract_from_snapshots(): snapshots = { 'messages.py': (), @@ -754,7 +743,6 @@ def extract_from_snapshots(): for filename, args in snapshots.items(): input_file = DATA_DIR / filename ->>>>>>> main output_file = input_file.with_suffix('.pot') contents = input_file.read_bytes() with temp_cwd(None): @@ -771,6 +759,7 @@ def update_POT_snapshots(): output_file.write_text(output, encoding='utf-8') + if __name__ == '__main__': # To regenerate POT files if len(sys.argv) > 1 and sys.argv[1] == '--snapshot-update': From 1f4e5acaaef576c63080acdd81c4c94c067d39e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric?= Date: Fri, 28 Feb 2025 13:20:35 -0500 Subject: [PATCH 10/36] fix details --- Lib/test/test_tools/test_i18n.py | 9 +++------ Tools/i18n/msgfmt.py | 1 + 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_tools/test_i18n.py b/Lib/test/test_tools/test_i18n.py index ce04d13735d84a..73499ef431046a 100644 --- a/Lib/test/test_tools/test_i18n.py +++ b/Lib/test/test_tools/test_i18n.py @@ -522,7 +522,7 @@ class Test_msgfmt(unittest.TestCase): bpo-35335 - bpo-9741 """ - script = os.path.join(toolsdir,'i18n', 'msgfmt.py') + script = os.path.join(toolsdir, 'i18n', 'msgfmt.py') # binary images of tiny po files # windows end of lines for first one @@ -651,8 +651,7 @@ def test_help(self): self.assertTrue(stderr.startswith( b'Generate binary message catalog from textual' b' translation description.' - ) - ) + )) def test_wrong(self): """Test wrong option""" @@ -661,8 +660,7 @@ def test_wrong(self): self.assertTrue(stderr.startswith( b'Generate binary message catalog from textual' b' translation description.' - ) - ) + )) def test_outputfile(self): """Test script with -o option - 1 single file, Windows EOL""" @@ -759,7 +757,6 @@ def update_POT_snapshots(): output_file.write_text(output, encoding='utf-8') - if __name__ == '__main__': # To regenerate POT files if len(sys.argv) > 1 and sys.argv[1] == '--snapshot-update': diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py index 59ebb6acfe67c3..4a6200ae91d07c 100755 --- a/Tools/i18n/msgfmt.py +++ b/Tools/i18n/msgfmt.py @@ -1,6 +1,7 @@ #! /usr/bin/env python3 # Written by Martin v. Löwis # Modified by s-ball + """Generate binary message catalog from textual translation description. This program converts a textual Uniforum-style message catalog (.po file) into From 46c08c58580f9708cdfc4e486de0c23c7b3790b5 Mon Sep 17 00:00:00 2001 From: s-ball Date: Sat, 1 Mar 2025 11:19:27 +0100 Subject: [PATCH 11/36] Move tests for the gh-79516 issue to test_msgfmt.py Those tests used to be in the test_i18n.py file. But as msgfmt now has its own test file, they have to go there. The test class has been renamed Test_multi_inputs. --- Lib/test/test_tools/test_i18n.py | 211 ----------------------------- Lib/test/test_tools/test_msgfmt.py | 211 +++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 211 deletions(-) diff --git a/Lib/test/test_tools/test_i18n.py b/Lib/test/test_tools/test_i18n.py index 73499ef431046a..a42d8865a4e0d8 100644 --- a/Lib/test/test_tools/test_i18n.py +++ b/Lib/test/test_tools/test_i18n.py @@ -517,217 +517,6 @@ def test_parse_keyword_spec(self): self.assertEqual(str(cm.exception), message) -class Test_msgfmt(unittest.TestCase): - """Tests for the msgfmt.py tool - bpo-35335 - bpo-9741 - """ - - script = os.path.join(toolsdir, 'i18n', 'msgfmt.py') - - # binary images of tiny po files - # windows end of lines for first one - file1_fr_po = b'''# French translations for python package.\r -# Copyright (C) 2018 THE python\'S COPYRIGHT HOLDER\r -# This file is distributed under the same license as the python package.\r -# s-ball , 2018.\r -#\r -msgid ""\r -msgstr ""\r -"Project-Id-Version: python 3.8\\n"\r -"Report-Msgid-Bugs-To: \\n"\r -"POT-Creation-Date: 2018-11-30 23:46+0100\\n"\r -"PO-Revision-Date: 2018-11-30 23:47+0100\\n"\r -"Last-Translator: s-ball \\n"\r -"Language-Team: French\\n"\r\n"Language: fr\\n"\r -"MIME-Version: 1.0\\n"\r -"Content-Type: text/plain; charset=UTF-8\\n"\r -"Content-Transfer-Encoding: 8bit\\n"\r -"Plural-Forms: nplurals=2; plural=(n > 1);\\n"\r -\r -#: file1.py:6\r -msgid "Hello!"\r -msgstr "Bonjour !"\r -\r -#: file1.py:7\r -#, python-brace-format\r -msgid "{n} horse"\r -msgid_plural "{n} horses"\r -msgstr[0] "{n} cheval"\r -msgstr[1] "{n} chevaux"\r -''' - - # Unix end of file and a non ascii character for second one - file2_fr_po = rb'''# French translations for python package. -# Copyright (C) 2018 THE python'S COPYRIGHT HOLDER -# This file is distributed under the same license as the python package. -# s-ball , 2018. -# -msgid "" -msgstr "" -"Project-Id-Version: python 3.8\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-11-30 23:57+0100\n" -"PO-Revision-Date: 2018-11-30 23:57+0100\n" -"Last-Translator: s-ball \n" -"Language-Team: French\n" -"Language: fr\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#: file2.py:6 -msgid "It's over." -msgstr "C'est termin\xc3\xa9." - -#: file2.py:7 -msgid "Bye..." -msgstr "Au revoir ..." -''' - - # binary images of corresponding compiled mo files - file1_fr_mo = b'\xde\x12\x04\x95\x00\x00\x00\x00\x03\x00\x00\x00\x1c' \ - b'\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ - b'\x00\x00\x00\x00\x00L\x00\x00\x00\x06\x00\x00\x00M' \ - b'\x00\x00\x00\x14\x00\x00\x00T\x00\x00\x00[\x01\x00' \ - b'\x00i\x00\x00\x00\t\x00\x00\x00\xc5\x01\x00\x00\x16' \ - b'\x00\x00\x00\xcf\x01\x00\x00\x00Hello!\x00{n} horse' \ - b'\x00{n} horses\x00Project-Id-Version: python 3.8\n' \ - b'Report-Msgid-Bugs-To: \nPOT-Creation-Date: 2018-11-30 ' \ - b'23:46+0100\nPO-Revision-Date: 2018-11-30 23:47+0100\n'\ - b'Last-Translator: s-ball \n' \ - b'Language-Team: French\nLanguage: fr\nMIME-Version: 1.0' \ - b'\nContent-Type: text/plain; charset=UTF-8\nContent-' \ - b'Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2' \ - b'; plural=(n > 1);\n\x00Bonjour !\x00{n} cheval\x00{n' \ - b'} chevaux\x00' - file2_fr_mo = b"\xde\x12\x04\x95\x00\x00\x00\x00\x03\x00\x00\x00" \ - b"\x1c\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00" \ - b"\x00\x00\x00\x00\x00\x00\x00L\x00\x00\x00\x06\x00" \ - b"\x00\x00M\x00\x00\x00\n\x00\x00\x00T\x00\x00\x00[" \ - b"\x01\x00\x00_\x00\x00\x00\r\x00\x00\x00\xbb\x01" \ - b"\x00\x00\x0f\x00\x00\x00\xc9\x01\x00\x00\x00Bye.." \ - b".\x00It's over.\x00Project-Id-Version: python 3.8" \ - b"\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2018" \ - b"-11-30 23:57+0100\nPO-Revision-Date: 2018-11-30 2" \ - b"3:57+0100\nLast-Translator: s-ball \nLanguage-Team: French\nLanguage: fr\nMIME" \ - b"-Version: 1.0\nContent-Type: text/plain; charset=" \ - b"UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Fo" \ - b"rms: nplurals=2; plural=(n > 1);\n\x00Au revoir ." \ - b"..\x00C'est termin\xc3\xa9.\x00" - - # image of merging both po files keeping second header - file12_fr_mo = b"\xde\x12\x04\x95\x00\x00\x00\x00\x05\x00\x00\x00" \ - b"\x1c\x00\x00\x00D\x00\x00\x00\x00\x00\x00\x00\x00" \ - b"\x00\x00\x00\x00\x00\x00\x00l\x00\x00\x00\x06\x00" \ - b"\x00\x00m\x00\x00\x00\x06\x00\x00\x00t\x00\x00\x00" \ - b"\n\x00\x00\x00{\x00\x00\x00\x14\x00\x00\x00\x86" \ - b"\x00\x00\x00[\x01\x00\x00\x9b\x00\x00\x00\r\x00" \ - b"\x00\x00\xf7\x01\x00\x00\t\x00\x00\x00\x05\x02\x00" \ - b"\x00\x0f\x00\x00\x00\x0f\x02\x00\x00\x16\x00\x00" \ - b"\x00\x1f\x02\x00\x00\x00Bye...\x00Hello!\x00It's " \ - b"over.\x00{n} horse\x00{n} horses\x00Project-Id-Ver" \ - b"sion: python 3.8\nReport-Msgid-Bugs-To: \nPOT-Crea" \ - b"tion-Date: 2018-11-30 23:57+0100\nPO-Revision-Date" \ - b": 2018-11-30 23:57+0100\nLast-Translator: s-ball <" \ - b"s-ball@laposte.net>\nLanguage-Team: French\nLangua" \ - b"ge: fr\nMIME-Version: 1.0\nContent-Type: text/plai" \ - b"n; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ - b"\nPlural-Forms: nplurals=2; plural=(n > 1);\n\x00A" \ - b"u revoir ...\x00Bonjour !\x00C'est termin\xc3\xa9." \ - b"\x00{n} cheval\x00{n} chevaux\x00" - def imp(self): - i18ndir = os.path.join(toolsdir, 'i18n') - sys.path.append(i18ndir) - import msgfmt - sys.path.remove(i18ndir) - return msgfmt - - def test_help(self): - """Test option -h""" - rc, stdout, stderr = assert_python_ok(self.script, '-h') - self.assertEqual(0, rc) - self.assertTrue(stderr.startswith( - b'Generate binary message catalog from textual' - b' translation description.' - )) - - def test_wrong(self): - """Test wrong option""" - rc, stdout, stderr = assert_python_failure(self.script, '-x') - self.assertEqual(1, rc) - self.assertTrue(stderr.startswith( - b'Generate binary message catalog from textual' - b' translation description.' - )) - - def test_outputfile(self): - """Test script with -o option - 1 single file, Windows EOL""" - with temp_cwd(None): - with open("file1_fr.po", "wb") as out: - out.write(self.file1_fr_po) - assert_python_ok(self.script, '-o', 'file1.mo', 'file1_fr.po') - with open('file1.mo', 'rb') as fin: - self.assertEqual(self.file1_fr_mo, fin.read()) - - def test_no_outputfile(self): - """Test script without -o option - 1 single file, Unix EOL""" - with temp_cwd(None): - with open("file2_fr.po", "wb") as out: - out.write(self.file2_fr_po) - assert_python_ok(self.script, 'file2_fr.po') - with open('file2_fr.mo', 'rb') as fin: - self.assertEqual(self.file2_fr_mo, fin.read()) - - def test_both_with_outputfile(self): - """Test script with -o option and 2 input files""" - # msgfmt.py version 1.2 behaviour is to correctly merge the input - # files and to keep last entry when same entry occurs in more than - # one file - with temp_cwd(None): - with open("file1_fr.po", "wb") as out: - out.write(self.file1_fr_po) - with open("file2_fr.po", "wb") as out: - out.write(self.file2_fr_po) - assert_python_ok(self.script, '-o', 'file1.mo', - 'file1_fr.po', 'file2_fr.po') - with open('file1.mo', 'rb') as fin: - self.assertEqual(self.file12_fr_mo, fin.read()) - - def test_both_without_outputfile(self): - """Test script without -o option and 2 input files""" - # msgfmt.py version 1.2 behaviour was that second mo file - # also merged previous po files - with temp_cwd(None): - with open("file1_fr.po", "wb") as out: - out.write(self.file1_fr_po) - with open("file2_fr.po", "wb") as out: - out.write(self.file2_fr_po) - assert_python_ok(self.script, 'file1_fr.po', 'file2_fr.po') - with open('file1_fr.mo', 'rb') as fin: - self.assertEqual(self.file1_fr_mo, fin.read()) - with open('file2_fr.mo', 'rb') as fin: - self.assertEqual(self.file2_fr_mo, fin.read()) - - def test_consecutive_make_calls(self): - """Directly calls make twice to prove bpo-9741 is fixed""" - sys.path.append(os.path.join(toolsdir,'i18n')) - from msgfmt import make - with temp_cwd(None): - with open("file1_fr.po", "wb") as out: - out.write(self.file1_fr_po) - with open("file2_fr.po", "wb") as out: - out.write(self.file2_fr_po) - make("file1_fr.po", "file1_fr.mo") - make("file2_fr.po", "file2_fr.mo") - with open('file1_fr.mo', 'rb') as fin: - self.assertEqual(self.file1_fr_mo, fin.read()) - with open('file2_fr.mo', 'rb') as fin: - self.assertEqual(self.file2_fr_mo, fin.read()) - sys.path.pop() - - def extract_from_snapshots(): snapshots = { 'messages.py': (), diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index a6073b8be03073..908e4d52d32646 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -121,6 +121,217 @@ def test_nonexistent_file(self): assert_python_failure(msgfmt, 'nonexistent.po') +class Test_multi_input(unittest.TestCase): + """Tests for the msgfmt.py tool + bpo-35335 - bpo-9741 + """ + + script = os.path.join(toolsdir, 'i18n', 'msgfmt.py') + + # binary images of tiny po files + # windows end of lines for first one + file1_fr_po = b'''# French translations for python package.\r +# Copyright (C) 2018 THE python\'S COPYRIGHT HOLDER\r +# This file is distributed under the same license as the python package.\r +# s-ball , 2018.\r +#\r +msgid ""\r +msgstr ""\r +"Project-Id-Version: python 3.8\\n"\r +"Report-Msgid-Bugs-To: \\n"\r +"POT-Creation-Date: 2018-11-30 23:46+0100\\n"\r +"PO-Revision-Date: 2018-11-30 23:47+0100\\n"\r +"Last-Translator: s-ball \\n"\r +"Language-Team: French\\n"\r\n"Language: fr\\n"\r +"MIME-Version: 1.0\\n"\r +"Content-Type: text/plain; charset=UTF-8\\n"\r +"Content-Transfer-Encoding: 8bit\\n"\r +"Plural-Forms: nplurals=2; plural=(n > 1);\\n"\r +\r +#: file1.py:6\r +msgid "Hello!"\r +msgstr "Bonjour !"\r +\r +#: file1.py:7\r +#, python-brace-format\r +msgid "{n} horse"\r +msgid_plural "{n} horses"\r +msgstr[0] "{n} cheval"\r +msgstr[1] "{n} chevaux"\r +''' + + # Unix end of file and a non ascii character for second one + file2_fr_po = rb'''# French translations for python package. +# Copyright (C) 2018 THE python'S COPYRIGHT HOLDER +# This file is distributed under the same license as the python package. +# s-ball , 2018. +# +msgid "" +msgstr "" +"Project-Id-Version: python 3.8\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-11-30 23:57+0100\n" +"PO-Revision-Date: 2018-11-30 23:57+0100\n" +"Last-Translator: s-ball \n" +"Language-Team: French\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: file2.py:6 +msgid "It's over." +msgstr "C'est termin\xc3\xa9." + +#: file2.py:7 +msgid "Bye..." +msgstr "Au revoir ..." +''' + + # binary images of corresponding compiled mo files + file1_fr_mo = b'\xde\x12\x04\x95\x00\x00\x00\x00\x03\x00\x00\x00\x1c' \ + b'\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ + b'\x00\x00\x00\x00\x00L\x00\x00\x00\x06\x00\x00\x00M' \ + b'\x00\x00\x00\x14\x00\x00\x00T\x00\x00\x00[\x01\x00' \ + b'\x00i\x00\x00\x00\t\x00\x00\x00\xc5\x01\x00\x00\x16' \ + b'\x00\x00\x00\xcf\x01\x00\x00\x00Hello!\x00{n} horse' \ + b'\x00{n} horses\x00Project-Id-Version: python 3.8\n' \ + b'Report-Msgid-Bugs-To: \nPOT-Creation-Date: 2018-11-30 ' \ + b'23:46+0100\nPO-Revision-Date: 2018-11-30 23:47+0100\n'\ + b'Last-Translator: s-ball \n' \ + b'Language-Team: French\nLanguage: fr\nMIME-Version: 1.0' \ + b'\nContent-Type: text/plain; charset=UTF-8\nContent-' \ + b'Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2' \ + b'; plural=(n > 1);\n\x00Bonjour !\x00{n} cheval\x00{n' \ + b'} chevaux\x00' + file2_fr_mo = b"\xde\x12\x04\x95\x00\x00\x00\x00\x03\x00\x00\x00" \ + b"\x1c\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00" \ + b"\x00\x00\x00\x00\x00\x00\x00L\x00\x00\x00\x06\x00" \ + b"\x00\x00M\x00\x00\x00\n\x00\x00\x00T\x00\x00\x00[" \ + b"\x01\x00\x00_\x00\x00\x00\r\x00\x00\x00\xbb\x01" \ + b"\x00\x00\x0f\x00\x00\x00\xc9\x01\x00\x00\x00Bye.." \ + b".\x00It's over.\x00Project-Id-Version: python 3.8" \ + b"\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2018" \ + b"-11-30 23:57+0100\nPO-Revision-Date: 2018-11-30 2" \ + b"3:57+0100\nLast-Translator: s-ball \nLanguage-Team: French\nLanguage: fr\nMIME" \ + b"-Version: 1.0\nContent-Type: text/plain; charset=" \ + b"UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Fo" \ + b"rms: nplurals=2; plural=(n > 1);\n\x00Au revoir ." \ + b"..\x00C'est termin\xc3\xa9.\x00" + + # image of merging both po files keeping second header + file12_fr_mo = b"\xde\x12\x04\x95\x00\x00\x00\x00\x05\x00\x00\x00" \ + b"\x1c\x00\x00\x00D\x00\x00\x00\x00\x00\x00\x00\x00" \ + b"\x00\x00\x00\x00\x00\x00\x00l\x00\x00\x00\x06\x00" \ + b"\x00\x00m\x00\x00\x00\x06\x00\x00\x00t\x00\x00\x00" \ + b"\n\x00\x00\x00{\x00\x00\x00\x14\x00\x00\x00\x86" \ + b"\x00\x00\x00[\x01\x00\x00\x9b\x00\x00\x00\r\x00" \ + b"\x00\x00\xf7\x01\x00\x00\t\x00\x00\x00\x05\x02\x00" \ + b"\x00\x0f\x00\x00\x00\x0f\x02\x00\x00\x16\x00\x00" \ + b"\x00\x1f\x02\x00\x00\x00Bye...\x00Hello!\x00It's " \ + b"over.\x00{n} horse\x00{n} horses\x00Project-Id-Ver" \ + b"sion: python 3.8\nReport-Msgid-Bugs-To: \nPOT-Crea" \ + b"tion-Date: 2018-11-30 23:57+0100\nPO-Revision-Date" \ + b": 2018-11-30 23:57+0100\nLast-Translator: s-ball <" \ + b"s-ball@laposte.net>\nLanguage-Team: French\nLangua" \ + b"ge: fr\nMIME-Version: 1.0\nContent-Type: text/plai" \ + b"n; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ + b"\nPlural-Forms: nplurals=2; plural=(n > 1);\n\x00A" \ + b"u revoir ...\x00Bonjour !\x00C'est termin\xc3\xa9." \ + b"\x00{n} cheval\x00{n} chevaux\x00" + def imp(self): + i18ndir = os.path.join(toolsdir, 'i18n') + sys.path.append(i18ndir) + import msgfmt + sys.path.remove(i18ndir) + return msgfmt + + def test_help(self): + """Test option -h""" + rc, stdout, stderr = assert_python_ok(self.script, '-h') + self.assertEqual(0, rc) + self.assertTrue(stderr.startswith( + b'Generate binary message catalog from textual' + b' translation description.' + )) + + def test_wrong(self): + """Test wrong option""" + rc, stdout, stderr = assert_python_failure(self.script, '-x') + self.assertEqual(1, rc) + self.assertTrue(stderr.startswith( + b'Generate binary message catalog from textual' + b' translation description.' + )) + + def test_outputfile(self): + """Test script with -o option - 1 single file, Windows EOL""" + with temp_cwd(None): + with open("file1_fr.po", "wb") as out: + out.write(self.file1_fr_po) + assert_python_ok(self.script, '-o', 'file1.mo', 'file1_fr.po') + with open('file1.mo', 'rb') as fin: + self.assertEqual(self.file1_fr_mo, fin.read()) + + def test_no_outputfile(self): + """Test script without -o option - 1 single file, Unix EOL""" + with temp_cwd(None): + with open("file2_fr.po", "wb") as out: + out.write(self.file2_fr_po) + assert_python_ok(self.script, 'file2_fr.po') + with open('file2_fr.mo', 'rb') as fin: + self.assertEqual(self.file2_fr_mo, fin.read()) + + def test_both_with_outputfile(self): + """Test script with -o option and 2 input files""" + # msgfmt.py version 1.2 behaviour is to correctly merge the input + # files and to keep last entry when same entry occurs in more than + # one file + with temp_cwd(None): + with open("file1_fr.po", "wb") as out: + out.write(self.file1_fr_po) + with open("file2_fr.po", "wb") as out: + out.write(self.file2_fr_po) + assert_python_ok(self.script, '-o', 'file1.mo', + 'file1_fr.po', 'file2_fr.po') + with open('file1.mo', 'rb') as fin: + self.assertEqual(self.file12_fr_mo, fin.read()) + + def test_both_without_outputfile(self): + """Test script without -o option and 2 input files""" + # msgfmt.py version 1.2 behaviour was that second mo file + # also merged previous po files + with temp_cwd(None): + with open("file1_fr.po", "wb") as out: + out.write(self.file1_fr_po) + with open("file2_fr.po", "wb") as out: + out.write(self.file2_fr_po) + assert_python_ok(self.script, 'file1_fr.po', 'file2_fr.po') + with open('file1_fr.mo', 'rb') as fin: + self.assertEqual(self.file1_fr_mo, fin.read()) + with open('file2_fr.mo', 'rb') as fin: + self.assertEqual(self.file2_fr_mo, fin.read()) + + def test_consecutive_make_calls(self): + """Directly calls make twice to prove bpo-9741 is fixed""" + sys.path.append(os.path.join(toolsdir,'i18n')) + from msgfmt import make + with temp_cwd(None): + with open("file1_fr.po", "wb") as out: + out.write(self.file1_fr_po) + with open("file2_fr.po", "wb") as out: + out.write(self.file2_fr_po) + make("file1_fr.po", "file1_fr.mo") + make("file2_fr.po", "file2_fr.mo") + with open('file1_fr.mo', 'rb') as fin: + self.assertEqual(self.file1_fr_mo, fin.read()) + with open('file2_fr.mo', 'rb') as fin: + self.assertEqual(self.file2_fr_mo, fin.read()) + sys.path.pop() + + def update_catalog_snapshots(): for po_file in data_dir.glob('*.po'): mo_file = po_file.with_suffix('.mo') From 24d89a66500235221d0563b66b68f678b9b28fb7 Mon Sep 17 00:00:00 2001 From: s-ball Date: Sat, 1 Mar 2025 11:44:48 +0100 Subject: [PATCH 12/36] Cosmetic improvements after review. --- Lib/test/test_tools/test_msgfmt.py | 122 ++++++++++-------- .../2018-12-05-20-46-10.bpo-35335.qtIUBx.rst | 2 +- 2 files changed, 66 insertions(+), 58 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 908e4d52d32646..ffa7a8e78562b5 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -9,7 +9,6 @@ from test.support.script_helper import assert_python_failure, assert_python_ok from test.test_tools import skip_if_missing, toolsdir - skip_if_missing('i18n') data_dir = (Path(__file__).parent / 'msgfmt_data').resolve() @@ -91,6 +90,7 @@ def test_generic_syntax_error(self): err = res.err.decode('utf-8') self.assertIn('Syntax error', err) + class CLITest(unittest.TestCase): def test_help(self): @@ -122,13 +122,14 @@ def test_nonexistent_file(self): class Test_multi_input(unittest.TestCase): - """Tests for the msgfmt.py tool - bpo-35335 - bpo-9741 + """Tests for the issue https://github.com/python/cpython/issues/79516 + msgfmt.py shall accept multiple input files and when imported + make shall be callable multiple times """ script = os.path.join(toolsdir, 'i18n', 'msgfmt.py') - # binary images of tiny po files + # binary contents of tiny po files # windows end of lines for first one file1_fr_po = b'''# French translations for python package.\r # Copyright (C) 2018 THE python\'S COPYRIGHT HOLDER\r @@ -189,58 +190,65 @@ class Test_multi_input(unittest.TestCase): msgstr "Au revoir ..." ''' - # binary images of corresponding compiled mo files - file1_fr_mo = b'\xde\x12\x04\x95\x00\x00\x00\x00\x03\x00\x00\x00\x1c' \ - b'\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \ - b'\x00\x00\x00\x00\x00L\x00\x00\x00\x06\x00\x00\x00M' \ - b'\x00\x00\x00\x14\x00\x00\x00T\x00\x00\x00[\x01\x00' \ - b'\x00i\x00\x00\x00\t\x00\x00\x00\xc5\x01\x00\x00\x16' \ - b'\x00\x00\x00\xcf\x01\x00\x00\x00Hello!\x00{n} horse' \ - b'\x00{n} horses\x00Project-Id-Version: python 3.8\n' \ - b'Report-Msgid-Bugs-To: \nPOT-Creation-Date: 2018-11-30 ' \ - b'23:46+0100\nPO-Revision-Date: 2018-11-30 23:47+0100\n'\ - b'Last-Translator: s-ball \n' \ - b'Language-Team: French\nLanguage: fr\nMIME-Version: 1.0' \ - b'\nContent-Type: text/plain; charset=UTF-8\nContent-' \ - b'Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2' \ - b'; plural=(n > 1);\n\x00Bonjour !\x00{n} cheval\x00{n' \ - b'} chevaux\x00' - file2_fr_mo = b"\xde\x12\x04\x95\x00\x00\x00\x00\x03\x00\x00\x00" \ - b"\x1c\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00" \ - b"\x00\x00\x00\x00\x00\x00\x00L\x00\x00\x00\x06\x00" \ - b"\x00\x00M\x00\x00\x00\n\x00\x00\x00T\x00\x00\x00[" \ - b"\x01\x00\x00_\x00\x00\x00\r\x00\x00\x00\xbb\x01" \ - b"\x00\x00\x0f\x00\x00\x00\xc9\x01\x00\x00\x00Bye.." \ - b".\x00It's over.\x00Project-Id-Version: python 3.8" \ - b"\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2018" \ - b"-11-30 23:57+0100\nPO-Revision-Date: 2018-11-30 2" \ - b"3:57+0100\nLast-Translator: s-ball \nLanguage-Team: French\nLanguage: fr\nMIME" \ - b"-Version: 1.0\nContent-Type: text/plain; charset=" \ - b"UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Fo" \ - b"rms: nplurals=2; plural=(n > 1);\n\x00Au revoir ." \ - b"..\x00C'est termin\xc3\xa9.\x00" - - # image of merging both po files keeping second header - file12_fr_mo = b"\xde\x12\x04\x95\x00\x00\x00\x00\x05\x00\x00\x00" \ - b"\x1c\x00\x00\x00D\x00\x00\x00\x00\x00\x00\x00\x00" \ - b"\x00\x00\x00\x00\x00\x00\x00l\x00\x00\x00\x06\x00" \ - b"\x00\x00m\x00\x00\x00\x06\x00\x00\x00t\x00\x00\x00" \ - b"\n\x00\x00\x00{\x00\x00\x00\x14\x00\x00\x00\x86" \ - b"\x00\x00\x00[\x01\x00\x00\x9b\x00\x00\x00\r\x00" \ - b"\x00\x00\xf7\x01\x00\x00\t\x00\x00\x00\x05\x02\x00" \ - b"\x00\x0f\x00\x00\x00\x0f\x02\x00\x00\x16\x00\x00" \ - b"\x00\x1f\x02\x00\x00\x00Bye...\x00Hello!\x00It's " \ - b"over.\x00{n} horse\x00{n} horses\x00Project-Id-Ver" \ - b"sion: python 3.8\nReport-Msgid-Bugs-To: \nPOT-Crea" \ - b"tion-Date: 2018-11-30 23:57+0100\nPO-Revision-Date" \ - b": 2018-11-30 23:57+0100\nLast-Translator: s-ball <" \ - b"s-ball@laposte.net>\nLanguage-Team: French\nLangua" \ - b"ge: fr\nMIME-Version: 1.0\nContent-Type: text/plai" \ - b"n; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ - b"\nPlural-Forms: nplurals=2; plural=(n > 1);\n\x00A" \ - b"u revoir ...\x00Bonjour !\x00C'est termin\xc3\xa9." \ - b"\x00{n} cheval\x00{n} chevaux\x00" + # binary contents of corresponding compiled mo files + file1_fr_mo = ( + b'\xde\x12\x04\x95\x00\x00\x00\x00\x03\x00\x00\x00\x1c' + b'\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00L\x00\x00\x00\x06\x00\x00\x00M' + b'\x00\x00\x00\x14\x00\x00\x00T\x00\x00\x00[\x01\x00' + b'\x00i\x00\x00\x00\t\x00\x00\x00\xc5\x01\x00\x00\x16' + b'\x00\x00\x00\xcf\x01\x00\x00\x00Hello!\x00{n} horse' + b'\x00{n} horses\x00Project-Id-Version: python 3.8\n' + b'Report-Msgid-Bugs-To: \nPOT-Creation-Date: 2018-11-30 ' + b'23:46+0100\nPO-Revision-Date: 2018-11-30 23:47+0100\n' + b'Last-Translator: s-ball \n' + b'Language-Team: French\nLanguage: fr\nMIME-Version: 1.0' + b'\nContent-Type: text/plain; charset=UTF-8\nContent-' + b'Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2' + b'; plural=(n > 1);\n\x00Bonjour !\x00{n} cheval\x00{n' + b'} chevaux\x00' + ) + file2_fr_mo = ( + b"\xde\x12\x04\x95\x00\x00\x00\x00\x03\x00\x00\x00" + b"\x1c\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00L\x00\x00\x00\x06\x00" + b"\x00\x00M\x00\x00\x00\n\x00\x00\x00T\x00\x00\x00[" + b"\x01\x00\x00_\x00\x00\x00\r\x00\x00\x00\xbb\x01" + b"\x00\x00\x0f\x00\x00\x00\xc9\x01\x00\x00\x00Bye.." + b".\x00It's over.\x00Project-Id-Version: python 3.8" + b"\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2018" + b"-11-30 23:57+0100\nPO-Revision-Date: 2018-11-30 2" + b"3:57+0100\nLast-Translator: s-ball \nLanguage-Team: French\nLanguage: fr\nMIME" + b"-Version: 1.0\nContent-Type: text/plain; charset=" + b"UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Fo" + b"rms: nplurals=2; plural=(n > 1);\n\x00Au revoir ." + b"..\x00C'est termin\xc3\xa9.\x00" + ) + + # content of merging both po files keeping second header + file12_fr_mo = ( + b"\xde\x12\x04\x95\x00\x00\x00\x00\x05\x00\x00\x00" + b"\x1c\x00\x00\x00D\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00l\x00\x00\x00\x06\x00" + b"\x00\x00m\x00\x00\x00\x06\x00\x00\x00t\x00\x00\x00" + b"\n\x00\x00\x00{\x00\x00\x00\x14\x00\x00\x00\x86" + b"\x00\x00\x00[\x01\x00\x00\x9b\x00\x00\x00\r\x00" + b"\x00\x00\xf7\x01\x00\x00\t\x00\x00\x00\x05\x02\x00" + b"\x00\x0f\x00\x00\x00\x0f\x02\x00\x00\x16\x00\x00" + b"\x00\x1f\x02\x00\x00\x00Bye...\x00Hello!\x00It's " + b"over.\x00{n} horse\x00{n} horses\x00Project-Id-Ver" + b"sion: python 3.8\nReport-Msgid-Bugs-To: \nPOT-Crea" + b"tion-Date: 2018-11-30 23:57+0100\nPO-Revision-Date" + b": 2018-11-30 23:57+0100\nLast-Translator: s-ball <" + b"s-ball@laposte.net>\nLanguage-Team: French\nLangua" + b"ge: fr\nMIME-Version: 1.0\nContent-Type: text/plai" + b"n; charset=UTF-8\nContent-Transfer-Encoding: 8bit" + b"\nPlural-Forms: nplurals=2; plural=(n > 1);\n\x00A" + b"u revoir ...\x00Bonjour !\x00C'est termin\xc3\xa9." + b"\x00{n} cheval\x00{n} chevaux\x00" + ) + def imp(self): i18ndir = os.path.join(toolsdir, 'i18n') sys.path.append(i18ndir) @@ -316,7 +324,7 @@ def test_both_without_outputfile(self): def test_consecutive_make_calls(self): """Directly calls make twice to prove bpo-9741 is fixed""" - sys.path.append(os.path.join(toolsdir,'i18n')) + sys.path.append(os.path.join(toolsdir, 'i18n')) from msgfmt import make with temp_cwd(None): with open("file1_fr.po", "wb") as out: diff --git a/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst b/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst index 451536f3f1bc55..f99a5bbc50b444 100644 --- a/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst +++ b/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst @@ -1,3 +1,3 @@ -It is now possible to merge more than one single po file into a compiled mo +The script ``Tools/i18n/msgfmt.py`` is now able to merge more than one single po file into a compiled mo file. When an entry exists in more than on input file, the last file wins. It is also possible to directly call ``make`` more than once From 17b4e05da8f5655efded8ca4f74997877989e6f9 Mon Sep 17 00:00:00 2001 From: s-ball Date: Sat, 1 Mar 2025 11:58:07 +0100 Subject: [PATCH 13/36] Fix an import error --- Lib/test/test_tools/test_msgfmt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index ffa7a8e78562b5..7636b465e6a0c2 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -1,5 +1,6 @@ """Tests for the Tools/i18n/msgfmt.py tool.""" +import os import sys import unittest from gettext import GNUTranslations From 106dd40033d5de0cfec579277eb91bef2b803a44 Mon Sep 17 00:00:00 2001 From: s-ball <43739830+s-ball@users.noreply.github.com> Date: Sun, 2 Mar 2025 09:18:31 +0100 Subject: [PATCH 14/36] Apply suggestions from code review Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> --- .../Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst | 2 +- Tools/i18n/msgfmt.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst b/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst index f99a5bbc50b444..e9e184925414f1 100644 --- a/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst +++ b/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst @@ -1,3 +1,3 @@ -The script ``Tools/i18n/msgfmt.py`` is now able to merge more than one single po file into a compiled mo +:program:`msgfmt.py` is now able to merge more than one single po file into a compiled mo file. When an entry exists in more than on input file, the last file wins. It is also possible to directly call ``make`` more than once diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py index 4a6200ae91d07c..5ea058ed30096d 100755 --- a/Tools/i18n/msgfmt.py +++ b/Tools/i18n/msgfmt.py @@ -1,6 +1,5 @@ #! /usr/bin/env python3 # Written by Martin v. Löwis -# Modified by s-ball """Generate binary message catalog from textual translation description. @@ -106,11 +105,11 @@ def generate(messages): def make(filenames, outfile): - """This function is now member of the public interface. + """ filenames is a string or an iterable of strings representing input file(s) outfile is a string for the name of an input file or None. - If it is not None, the output file receives a merge of the input files + If it is not None, the output file receives a merge of the input files. If it is None, then filenames must be a string and the name of the output file is obtained by replacing the po extension with mo. Both ways are for compatibility reasons with previous behaviour. From 9cb9395fe6f24ccab4f93a41c349f19d85036a6c Mon Sep 17 00:00:00 2001 From: s-ball Date: Sun, 2 Mar 2025 12:06:55 +0100 Subject: [PATCH 15/36] Cosmetic improvements after review. Only changes documentation strings, comments, unused imports or blank lines --- Lib/test/test_tools/test_i18n.py | 2 +- Lib/test/test_tools/test_msgfmt.py | 1 + .../next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst | 3 +-- Tools/i18n/msgfmt.py | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_tools/test_i18n.py b/Lib/test/test_tools/test_i18n.py index a42d8865a4e0d8..d73fcff4c9cb11 100644 --- a/Lib/test/test_tools/test_i18n.py +++ b/Lib/test/test_tools/test_i18n.py @@ -7,7 +7,7 @@ from textwrap import dedent from pathlib import Path -from test.support.script_helper import assert_python_ok, assert_python_failure +from test.support.script_helper import assert_python_ok from test.test_tools import imports_under_tool, skip_if_missing, toolsdir from test.support.os_helper import temp_cwd, temp_dir diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 7636b465e6a0c2..5b7ddc9feef20e 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -10,6 +10,7 @@ from test.support.script_helper import assert_python_failure, assert_python_ok from test.test_tools import skip_if_missing, toolsdir + skip_if_missing('i18n') data_dir = (Path(__file__).parent / 'msgfmt_data').resolve() diff --git a/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst b/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst index e9e184925414f1..4df2309a1c3fd1 100644 --- a/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst +++ b/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst @@ -1,3 +1,2 @@ -:program:`msgfmt.py` is now able to merge more than one single po file into a compiled mo +:program:`msgfmt.py` is now able to merge more than one single po file into a compiled mo file. When an entry exists in more than on input file, the last file wins. -It is also possible to directly call ``make`` more than once diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py index 5ea058ed30096d..da1e77a8d1fffa 100755 --- a/Tools/i18n/msgfmt.py +++ b/Tools/i18n/msgfmt.py @@ -105,7 +105,8 @@ def generate(messages): def make(filenames, outfile): - """ + """ Compiles one or several po files(s). + filenames is a string or an iterable of strings representing input file(s) outfile is a string for the name of an input file or None. From 08bc8d7fac8718a8c1699f8a1ea5b3804d56a272 Mon Sep 17 00:00:00 2001 From: s-ball Date: Sun, 2 Mar 2025 15:33:55 +0100 Subject: [PATCH 16/36] In test_msgfmt move data files to the data folder. Data used by test_msgfmt.Test_multi_input are now stored as files inside the msgfmt_data directory. --- .../test_tools/msgfmt_data/.gitattributes | 2 + Lib/test/test_tools/msgfmt_data/file12_fr.mo | Bin 0 -> 566 bytes Lib/test/test_tools/msgfmt_data/file1_fr.mo | Bin 0 -> 486 bytes Lib/test/test_tools/msgfmt_data/file1_fr.po | 29 +++ Lib/test/test_tools/msgfmt_data/file2_fr.mo | Bin 0 -> 473 bytes Lib/test/test_tools/msgfmt_data/file2_fr.po | 26 +++ Lib/test/test_tools/test_msgfmt.py | 171 +++--------------- 7 files changed, 82 insertions(+), 146 deletions(-) create mode 100644 Lib/test/test_tools/msgfmt_data/.gitattributes create mode 100644 Lib/test/test_tools/msgfmt_data/file12_fr.mo create mode 100644 Lib/test/test_tools/msgfmt_data/file1_fr.mo create mode 100644 Lib/test/test_tools/msgfmt_data/file1_fr.po create mode 100644 Lib/test/test_tools/msgfmt_data/file2_fr.mo create mode 100644 Lib/test/test_tools/msgfmt_data/file2_fr.po diff --git a/Lib/test/test_tools/msgfmt_data/.gitattributes b/Lib/test/test_tools/msgfmt_data/.gitattributes new file mode 100644 index 00000000000000..9f9e93badb2194 --- /dev/null +++ b/Lib/test/test_tools/msgfmt_data/.gitattributes @@ -0,0 +1,2 @@ +file1_fr.po eol=crlf +file2_fr.po eol=lf diff --git a/Lib/test/test_tools/msgfmt_data/file12_fr.mo b/Lib/test/test_tools/msgfmt_data/file12_fr.mo new file mode 100644 index 0000000000000000000000000000000000000000..27100d8a4d1a959ef0f1fbf93708566d9a3eb30d GIT binary patch literal 566 zcmaKp!D<^Z5Qde~6uM9Xq4ZSf>C2)1QLZHtRL(cv(DHhW#_v?2g2YSU0Sf ztRJlJTSEL|zF_@h|D3t~M2H>c4)YcB4O8?N7{{^rg2uQ#F{1qdZjL?{H})1v=L2qB z5EJjNFef>h%5UgH<*b5wL8Y_Mjyusg*3Oe0hoYLwep3XQIR(+=ERzF|nm8|yHKBr5 zl6GX8%60;+w)*h#eUc^#2jw}=tL3?m=1z4WnR#u4(d4{>ATP8r@M+D@M%OM7#un)? zI@Pvlbb&HMJyS6BX!G)ZSHY!^#-s7c=0RzkL<46DEnl*zxm&_(I;f4VY!~uU^QTbn zYc`afO>WuDC3<;cb2qJQp`de7Q8Y1)*G3MVp9KZBUX`KO>cZOg-dZ?>^j$X+M-6zK oyUN3IAbn@AT;pL+4E8Y)x8P@${quW0n7qWfHmk|3Y;!IC18zKYwNJ2&^7(OP)Q*YWD$xvhps^tr@yBhrp6!i2A$o9!cyS3wQ{KFkyTF9 zI$XNzT#8K9Ax;G?#WkLzF01Ww>tUv7SVzTrlBtA4ZJarOC<`Mb>uNK%98|t(ZKGU= gt!-Q=XhL{d_q~=@GhG?NF8(S>BA1^J5(*OVf literal 0 HcmV?d00001 diff --git a/Lib/test/test_tools/msgfmt_data/file1_fr.po b/Lib/test/test_tools/msgfmt_data/file1_fr.po new file mode 100644 index 00000000000000..ddb03784de9a70 --- /dev/null +++ b/Lib/test/test_tools/msgfmt_data/file1_fr.po @@ -0,0 +1,29 @@ +# French translations for python package. +# Copyright (C) 2018 THE python\'S COPYRIGHT HOLDER +# This file is distributed under the same license as the python package. +# s-ball , 2018. +# +msgid "" +msgstr "" +"Project-Id-Version: python 3.8\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-11-30 23:46+0100\n" +"PO-Revision-Date: 2018-11-30 23:47+0100\n" +"Last-Translator: s-ball \n" +"Language-Team: French\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: file1.py:6 +msgid "Hello!" +msgstr "Bonjour !" + +#: file1.py:7 +#, python-brace-format +msgid "{n} horse" +msgid_plural "{n} horses" +msgstr[0] "{n} cheval" +msgstr[1] "{n} chevaux" diff --git a/Lib/test/test_tools/msgfmt_data/file2_fr.mo b/Lib/test/test_tools/msgfmt_data/file2_fr.mo new file mode 100644 index 0000000000000000000000000000000000000000..c466a9b9acf2f9fda1b89952e78ea03708e2eba7 GIT binary patch literal 473 zcmaKo!Ab)$5QbM24+|oA_As|9l6ALLTvA)KwS__#OIf{mkaRoUMz+b4q{Tjn58*R- z^yEwUEY4a`PY!(i|Ae2+B%cR6FCNu4aY&q1&7T^Q-z5^lCsN{J%kv&d_lP(0`=lSF zUT2L#5O@Puv!GXK0&isW6K0MLrtBV#m0EEq*KV#AG=kV4W2uc}iOuDdb(XnhspjC1 zZd2AZC>-V4jc~}J9!4>XBGw3@-r(mKCt(zZ6l7yu$?D#Jb3`J-ZKM8W#nEG!3Aez&$&Z^Fj5 hjuf;ZI&S*j^%4xOv@}qCdbftw(O|QX>isqFz5t>!gJ%E$ literal 0 HcmV?d00001 diff --git a/Lib/test/test_tools/msgfmt_data/file2_fr.po b/Lib/test/test_tools/msgfmt_data/file2_fr.po new file mode 100644 index 00000000000000..acd559492afe19 --- /dev/null +++ b/Lib/test/test_tools/msgfmt_data/file2_fr.po @@ -0,0 +1,26 @@ +# French translations for python package. +# Copyright (C) 2018 THE python'S COPYRIGHT HOLDER +# This file is distributed under the same license as the python package. +# s-ball , 2018. +# +msgid "" +msgstr "" +"Project-Id-Version: python 3.8\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-11-30 23:57+0100\n" +"PO-Revision-Date: 2018-11-30 23:57+0100\n" +"Last-Translator: s-ball \n" +"Language-Team: French\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: file2.py:6 +msgid "It's over." +msgstr "C'est terminé." + +#: file2.py:7 +msgid "Bye..." +msgstr "Au revoir ..." diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 5b7ddc9feef20e..c40ed6dac86b3f 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -1,6 +1,7 @@ """Tests for the Tools/i18n/msgfmt.py tool.""" - +import filecmp import os +import shutil import sys import unittest from gettext import GNUTranslations @@ -131,126 +132,6 @@ class Test_multi_input(unittest.TestCase): script = os.path.join(toolsdir, 'i18n', 'msgfmt.py') - # binary contents of tiny po files - # windows end of lines for first one - file1_fr_po = b'''# French translations for python package.\r -# Copyright (C) 2018 THE python\'S COPYRIGHT HOLDER\r -# This file is distributed under the same license as the python package.\r -# s-ball , 2018.\r -#\r -msgid ""\r -msgstr ""\r -"Project-Id-Version: python 3.8\\n"\r -"Report-Msgid-Bugs-To: \\n"\r -"POT-Creation-Date: 2018-11-30 23:46+0100\\n"\r -"PO-Revision-Date: 2018-11-30 23:47+0100\\n"\r -"Last-Translator: s-ball \\n"\r -"Language-Team: French\\n"\r\n"Language: fr\\n"\r -"MIME-Version: 1.0\\n"\r -"Content-Type: text/plain; charset=UTF-8\\n"\r -"Content-Transfer-Encoding: 8bit\\n"\r -"Plural-Forms: nplurals=2; plural=(n > 1);\\n"\r -\r -#: file1.py:6\r -msgid "Hello!"\r -msgstr "Bonjour !"\r -\r -#: file1.py:7\r -#, python-brace-format\r -msgid "{n} horse"\r -msgid_plural "{n} horses"\r -msgstr[0] "{n} cheval"\r -msgstr[1] "{n} chevaux"\r -''' - - # Unix end of file and a non ascii character for second one - file2_fr_po = rb'''# French translations for python package. -# Copyright (C) 2018 THE python'S COPYRIGHT HOLDER -# This file is distributed under the same license as the python package. -# s-ball , 2018. -# -msgid "" -msgstr "" -"Project-Id-Version: python 3.8\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-11-30 23:57+0100\n" -"PO-Revision-Date: 2018-11-30 23:57+0100\n" -"Last-Translator: s-ball \n" -"Language-Team: French\n" -"Language: fr\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#: file2.py:6 -msgid "It's over." -msgstr "C'est termin\xc3\xa9." - -#: file2.py:7 -msgid "Bye..." -msgstr "Au revoir ..." -''' - - # binary contents of corresponding compiled mo files - file1_fr_mo = ( - b'\xde\x12\x04\x95\x00\x00\x00\x00\x03\x00\x00\x00\x1c' - b'\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00L\x00\x00\x00\x06\x00\x00\x00M' - b'\x00\x00\x00\x14\x00\x00\x00T\x00\x00\x00[\x01\x00' - b'\x00i\x00\x00\x00\t\x00\x00\x00\xc5\x01\x00\x00\x16' - b'\x00\x00\x00\xcf\x01\x00\x00\x00Hello!\x00{n} horse' - b'\x00{n} horses\x00Project-Id-Version: python 3.8\n' - b'Report-Msgid-Bugs-To: \nPOT-Creation-Date: 2018-11-30 ' - b'23:46+0100\nPO-Revision-Date: 2018-11-30 23:47+0100\n' - b'Last-Translator: s-ball \n' - b'Language-Team: French\nLanguage: fr\nMIME-Version: 1.0' - b'\nContent-Type: text/plain; charset=UTF-8\nContent-' - b'Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2' - b'; plural=(n > 1);\n\x00Bonjour !\x00{n} cheval\x00{n' - b'} chevaux\x00' - ) - file2_fr_mo = ( - b"\xde\x12\x04\x95\x00\x00\x00\x00\x03\x00\x00\x00" - b"\x1c\x00\x00\x004\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00L\x00\x00\x00\x06\x00" - b"\x00\x00M\x00\x00\x00\n\x00\x00\x00T\x00\x00\x00[" - b"\x01\x00\x00_\x00\x00\x00\r\x00\x00\x00\xbb\x01" - b"\x00\x00\x0f\x00\x00\x00\xc9\x01\x00\x00\x00Bye.." - b".\x00It's over.\x00Project-Id-Version: python 3.8" - b"\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2018" - b"-11-30 23:57+0100\nPO-Revision-Date: 2018-11-30 2" - b"3:57+0100\nLast-Translator: s-ball \nLanguage-Team: French\nLanguage: fr\nMIME" - b"-Version: 1.0\nContent-Type: text/plain; charset=" - b"UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Fo" - b"rms: nplurals=2; plural=(n > 1);\n\x00Au revoir ." - b"..\x00C'est termin\xc3\xa9.\x00" - ) - - # content of merging both po files keeping second header - file12_fr_mo = ( - b"\xde\x12\x04\x95\x00\x00\x00\x00\x05\x00\x00\x00" - b"\x1c\x00\x00\x00D\x00\x00\x00\x00\x00\x00\x00\x00" - b"\x00\x00\x00\x00\x00\x00\x00l\x00\x00\x00\x06\x00" - b"\x00\x00m\x00\x00\x00\x06\x00\x00\x00t\x00\x00\x00" - b"\n\x00\x00\x00{\x00\x00\x00\x14\x00\x00\x00\x86" - b"\x00\x00\x00[\x01\x00\x00\x9b\x00\x00\x00\r\x00" - b"\x00\x00\xf7\x01\x00\x00\t\x00\x00\x00\x05\x02\x00" - b"\x00\x0f\x00\x00\x00\x0f\x02\x00\x00\x16\x00\x00" - b"\x00\x1f\x02\x00\x00\x00Bye...\x00Hello!\x00It's " - b"over.\x00{n} horse\x00{n} horses\x00Project-Id-Ver" - b"sion: python 3.8\nReport-Msgid-Bugs-To: \nPOT-Crea" - b"tion-Date: 2018-11-30 23:57+0100\nPO-Revision-Date" - b": 2018-11-30 23:57+0100\nLast-Translator: s-ball <" - b"s-ball@laposte.net>\nLanguage-Team: French\nLangua" - b"ge: fr\nMIME-Version: 1.0\nContent-Type: text/plai" - b"n; charset=UTF-8\nContent-Transfer-Encoding: 8bit" - b"\nPlural-Forms: nplurals=2; plural=(n > 1);\n\x00A" - b"u revoir ...\x00Bonjour !\x00C'est termin\xc3\xa9." - b"\x00{n} cheval\x00{n} chevaux\x00" - ) - def imp(self): i18ndir = os.path.join(toolsdir, 'i18n') sys.path.append(i18ndir) @@ -279,20 +160,20 @@ def test_wrong(self): def test_outputfile(self): """Test script with -o option - 1 single file, Windows EOL""" with temp_cwd(None): - with open("file1_fr.po", "wb") as out: - out.write(self.file1_fr_po) - assert_python_ok(self.script, '-o', 'file1.mo', 'file1_fr.po') - with open('file1.mo', 'rb') as fin: - self.assertEqual(self.file1_fr_mo, fin.read()) + assert_python_ok(self.script, '-o', 'file1.mo', + data_dir / 'file1_fr.po') + self.assertTrue( + filecmp.cmp(data_dir / 'file1_fr.mo', 'file1.mo'), + 'Wrong compiled file1_fr.mo') def test_no_outputfile(self): """Test script without -o option - 1 single file, Unix EOL""" with temp_cwd(None): - with open("file2_fr.po", "wb") as out: - out.write(self.file2_fr_po) + shutil.copy(data_dir / 'file2_fr.po', '.') assert_python_ok(self.script, 'file2_fr.po') - with open('file2_fr.mo', 'rb') as fin: - self.assertEqual(self.file2_fr_mo, fin.read()) + self.assertTrue( + filecmp.cmp(data_dir / 'file2_fr.mo', 'file2_fr.mo'), + 'Wrong compiled file2_fr.mo') def test_both_with_outputfile(self): """Test script with -o option and 2 input files""" @@ -300,29 +181,27 @@ def test_both_with_outputfile(self): # files and to keep last entry when same entry occurs in more than # one file with temp_cwd(None): - with open("file1_fr.po", "wb") as out: - out.write(self.file1_fr_po) - with open("file2_fr.po", "wb") as out: - out.write(self.file2_fr_po) - assert_python_ok(self.script, '-o', 'file1.mo', - 'file1_fr.po', 'file2_fr.po') - with open('file1.mo', 'rb') as fin: - self.assertEqual(self.file12_fr_mo, fin.read()) + assert_python_ok(self.script, '-o', 'file12.mo', + data_dir / 'file1_fr.po', + data_dir / 'file2_fr.po') + self.assertTrue( + filecmp.cmp(data_dir / 'file12_fr.mo', 'file12.mo'), + 'Wrong compiled file12.mo') def test_both_without_outputfile(self): """Test script without -o option and 2 input files""" # msgfmt.py version 1.2 behaviour was that second mo file # also merged previous po files with temp_cwd(None): - with open("file1_fr.po", "wb") as out: - out.write(self.file1_fr_po) - with open("file2_fr.po", "wb") as out: - out.write(self.file2_fr_po) + shutil.copy(data_dir /'file1_fr.po', '.') + shutil.copy(data_dir /'file2_fr.po', '.') assert_python_ok(self.script, 'file1_fr.po', 'file2_fr.po') - with open('file1_fr.mo', 'rb') as fin: - self.assertEqual(self.file1_fr_mo, fin.read()) - with open('file2_fr.mo', 'rb') as fin: - self.assertEqual(self.file2_fr_mo, fin.read()) + self.assertTrue( + filecmp.cmp(data_dir / 'file1_fr.mo', 'file1_fr.mo'), + 'Wrong compiled file1_fr.mo') + self.assertTrue( + filecmp.cmp(data_dir / 'file2_fr.mo', 'file2_fr.mo'), + 'Wrong compiled file2_fr.mo') def test_consecutive_make_calls(self): """Directly calls make twice to prove bpo-9741 is fixed""" From 9d992cd632d980a42a8cfeff133250d4b7db25f2 Mon Sep 17 00:00:00 2001 From: s-ball Date: Sun, 2 Mar 2025 15:47:48 +0100 Subject: [PATCH 17/36] Remove duplicate tests from test_msgfmt. A number of tests that had initially been created for Test_multi_inputs now exists in other classes. Additionally, one test was about calling an inner function which is an implementation detail. --- Lib/test/test_tools/test_msgfmt.py | 79 ++++++------------------------ 1 file changed, 14 insertions(+), 65 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index c40ed6dac86b3f..a58f0c677eb4b3 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -126,62 +126,29 @@ def test_nonexistent_file(self): class Test_multi_input(unittest.TestCase): """Tests for the issue https://github.com/python/cpython/issues/79516 - msgfmt.py shall accept multiple input files and when imported - make shall be callable multiple times + msgfmt.py shall accept multiple input files """ - script = os.path.join(toolsdir, 'i18n', 'msgfmt.py') - - def imp(self): - i18ndir = os.path.join(toolsdir, 'i18n') - sys.path.append(i18ndir) - import msgfmt - sys.path.remove(i18ndir) - return msgfmt - - def test_help(self): - """Test option -h""" - rc, stdout, stderr = assert_python_ok(self.script, '-h') - self.assertEqual(0, rc) - self.assertTrue(stderr.startswith( - b'Generate binary message catalog from textual' - b' translation description.' - )) - - def test_wrong(self): - """Test wrong option""" - rc, stdout, stderr = assert_python_failure(self.script, '-x') - self.assertEqual(1, rc) - self.assertTrue(stderr.startswith( - b'Generate binary message catalog from textual' - b' translation description.' - )) - - def test_outputfile(self): - """Test script with -o option - 1 single file, Windows EOL""" - with temp_cwd(None): - assert_python_ok(self.script, '-o', 'file1.mo', - data_dir / 'file1_fr.po') - self.assertTrue( - filecmp.cmp(data_dir / 'file1_fr.mo', 'file1.mo'), - 'Wrong compiled file1_fr.mo') - def test_no_outputfile(self): - """Test script without -o option - 1 single file, Unix EOL""" + """Test script without -o option - 1 single file""" with temp_cwd(None): shutil.copy(data_dir / 'file2_fr.po', '.') - assert_python_ok(self.script, 'file2_fr.po') + assert_python_ok(msgfmt, 'file2_fr.po') self.assertTrue( filecmp.cmp(data_dir / 'file2_fr.mo', 'file2_fr.mo'), 'Wrong compiled file2_fr.mo') def test_both_with_outputfile(self): - """Test script with -o option and 2 input files""" - # msgfmt.py version 1.2 behaviour is to correctly merge the input - # files and to keep last entry when same entry occurs in more than - # one file + """Test script with -o option and 2 input files + + The current behaviour is to merge entries having distinct ids + and keep last one if the same id occurs in multiple files. + + Here the first file has Windows endings (cflr) while second has + Unix endings (lf) + """ with temp_cwd(None): - assert_python_ok(self.script, '-o', 'file12.mo', + assert_python_ok(msgfmt, '-o', 'file12.mo', data_dir / 'file1_fr.po', data_dir / 'file2_fr.po') self.assertTrue( @@ -190,12 +157,11 @@ def test_both_with_outputfile(self): def test_both_without_outputfile(self): """Test script without -o option and 2 input files""" - # msgfmt.py version 1.2 behaviour was that second mo file - # also merged previous po files + with temp_cwd(None): shutil.copy(data_dir /'file1_fr.po', '.') shutil.copy(data_dir /'file2_fr.po', '.') - assert_python_ok(self.script, 'file1_fr.po', 'file2_fr.po') + assert_python_ok(msgfmt, 'file1_fr.po', 'file2_fr.po') self.assertTrue( filecmp.cmp(data_dir / 'file1_fr.mo', 'file1_fr.mo'), 'Wrong compiled file1_fr.mo') @@ -203,23 +169,6 @@ def test_both_without_outputfile(self): filecmp.cmp(data_dir / 'file2_fr.mo', 'file2_fr.mo'), 'Wrong compiled file2_fr.mo') - def test_consecutive_make_calls(self): - """Directly calls make twice to prove bpo-9741 is fixed""" - sys.path.append(os.path.join(toolsdir, 'i18n')) - from msgfmt import make - with temp_cwd(None): - with open("file1_fr.po", "wb") as out: - out.write(self.file1_fr_po) - with open("file2_fr.po", "wb") as out: - out.write(self.file2_fr_po) - make("file1_fr.po", "file1_fr.mo") - make("file2_fr.po", "file2_fr.mo") - with open('file1_fr.mo', 'rb') as fin: - self.assertEqual(self.file1_fr_mo, fin.read()) - with open('file2_fr.mo', 'rb') as fin: - self.assertEqual(self.file2_fr_mo, fin.read()) - sys.path.pop() - def update_catalog_snapshots(): for po_file in data_dir.glob('*.po'): From 51fcf098c2c52b9708fa7d6da203152d0084fa57 Mon Sep 17 00:00:00 2001 From: s-ball Date: Mon, 3 Mar 2025 20:04:25 +0100 Subject: [PATCH 18/36] Rename data files for test_msgfmt.Test_multi_input One po file has Windows type end of lines (crlf), while the other has Unix ones (lf). Their names now reflect that. --- .../test_tools/msgfmt_data/.gitattributes | 4 +-- .../{file1_fr.mo => file1_fr_crlf.mo} | Bin .../{file1_fr.po => file1_fr_crlf.po} | 0 .../{file2_fr.mo => file2_fr_lf.mo} | Bin .../{file2_fr.po => file2_fr_lf.po} | 0 Lib/test/test_tools/test_msgfmt.py | 29 ++++++++++-------- 6 files changed, 18 insertions(+), 15 deletions(-) rename Lib/test/test_tools/msgfmt_data/{file1_fr.mo => file1_fr_crlf.mo} (100%) rename Lib/test/test_tools/msgfmt_data/{file1_fr.po => file1_fr_crlf.po} (100%) rename Lib/test/test_tools/msgfmt_data/{file2_fr.mo => file2_fr_lf.mo} (100%) rename Lib/test/test_tools/msgfmt_data/{file2_fr.po => file2_fr_lf.po} (100%) diff --git a/Lib/test/test_tools/msgfmt_data/.gitattributes b/Lib/test/test_tools/msgfmt_data/.gitattributes index 9f9e93badb2194..a3712e3a1f9dc2 100644 --- a/Lib/test/test_tools/msgfmt_data/.gitattributes +++ b/Lib/test/test_tools/msgfmt_data/.gitattributes @@ -1,2 +1,2 @@ -file1_fr.po eol=crlf -file2_fr.po eol=lf +file1_fr_crlf.po eol=crlf +file2_fr_lf.po eol=lf diff --git a/Lib/test/test_tools/msgfmt_data/file1_fr.mo b/Lib/test/test_tools/msgfmt_data/file1_fr_crlf.mo similarity index 100% rename from Lib/test/test_tools/msgfmt_data/file1_fr.mo rename to Lib/test/test_tools/msgfmt_data/file1_fr_crlf.mo diff --git a/Lib/test/test_tools/msgfmt_data/file1_fr.po b/Lib/test/test_tools/msgfmt_data/file1_fr_crlf.po similarity index 100% rename from Lib/test/test_tools/msgfmt_data/file1_fr.po rename to Lib/test/test_tools/msgfmt_data/file1_fr_crlf.po diff --git a/Lib/test/test_tools/msgfmt_data/file2_fr.mo b/Lib/test/test_tools/msgfmt_data/file2_fr_lf.mo similarity index 100% rename from Lib/test/test_tools/msgfmt_data/file2_fr.mo rename to Lib/test/test_tools/msgfmt_data/file2_fr_lf.mo diff --git a/Lib/test/test_tools/msgfmt_data/file2_fr.po b/Lib/test/test_tools/msgfmt_data/file2_fr_lf.po similarity index 100% rename from Lib/test/test_tools/msgfmt_data/file2_fr.po rename to Lib/test/test_tools/msgfmt_data/file2_fr_lf.po diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index a58f0c677eb4b3..9d56bbe45d358f 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -132,11 +132,11 @@ class Test_multi_input(unittest.TestCase): def test_no_outputfile(self): """Test script without -o option - 1 single file""" with temp_cwd(None): - shutil.copy(data_dir / 'file2_fr.po', '.') - assert_python_ok(msgfmt, 'file2_fr.po') + shutil.copy(data_dir / 'file2_fr_lf.po', '.') + assert_python_ok(msgfmt, 'file2_fr_lf.po') self.assertTrue( - filecmp.cmp(data_dir / 'file2_fr.mo', 'file2_fr.mo'), - 'Wrong compiled file2_fr.mo') + filecmp.cmp(data_dir / 'file2_fr_lf.mo', 'file2_fr_lf.mo'), + 'Wrong compiled file2_fr_lf.mo') def test_both_with_outputfile(self): """Test script with -o option and 2 input files @@ -149,8 +149,8 @@ def test_both_with_outputfile(self): """ with temp_cwd(None): assert_python_ok(msgfmt, '-o', 'file12.mo', - data_dir / 'file1_fr.po', - data_dir / 'file2_fr.po') + data_dir / 'file1_fr_crlf.po', + data_dir / 'file2_fr_lf.po') self.assertTrue( filecmp.cmp(data_dir / 'file12_fr.mo', 'file12.mo'), 'Wrong compiled file12.mo') @@ -159,21 +159,24 @@ def test_both_without_outputfile(self): """Test script without -o option and 2 input files""" with temp_cwd(None): - shutil.copy(data_dir /'file1_fr.po', '.') - shutil.copy(data_dir /'file2_fr.po', '.') - assert_python_ok(msgfmt, 'file1_fr.po', 'file2_fr.po') + shutil.copy(data_dir /'file1_fr_crlf.po', '.') + shutil.copy(data_dir /'file2_fr_lf.po', '.') + assert_python_ok(msgfmt, 'file1_fr_crlf.po', 'file2_fr_lf.po') self.assertTrue( - filecmp.cmp(data_dir / 'file1_fr.mo', 'file1_fr.mo'), - 'Wrong compiled file1_fr.mo') + filecmp.cmp(data_dir / 'file1_fr_crlf.mo', 'file1_fr_crlf.mo'), + 'Wrong compiled file1_fr_crlf.mo') self.assertTrue( - filecmp.cmp(data_dir / 'file2_fr.mo', 'file2_fr.mo'), - 'Wrong compiled file2_fr.mo') + filecmp.cmp(data_dir / 'file2_fr_lf.mo', 'file2_fr_lf.mo'), + 'Wrong compiled file2_fr_lf.mo') def update_catalog_snapshots(): for po_file in data_dir.glob('*.po'): mo_file = po_file.with_suffix('.mo') compile_messages(po_file, mo_file) + assert_python_ok(msgfmt, '-o', data_dir /'file12_fr.mo', + data_dir / 'file1_fr_crlf.po', + data_dir / 'file2_fr_lf.po') if __name__ == '__main__': From d51ad506a0f1945661452e3908878cd0e3ce5d60 Mon Sep 17 00:00:00 2001 From: s-ball <43739830+s-ball@users.noreply.github.com> Date: Mon, 3 Mar 2025 23:10:35 +0100 Subject: [PATCH 19/36] Apply suggestions from code review Co-authored-by: Tomas R. Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> --- Lib/test/test_tools/test_msgfmt.py | 4 ++-- .../Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst | 2 +- Tools/i18n/msgfmt.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 9d56bbe45d358f..bfb6e8da102c08 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -124,9 +124,9 @@ def test_nonexistent_file(self): assert_python_failure(msgfmt, 'nonexistent.po') -class Test_multi_input(unittest.TestCase): +class MultiInputTest(unittest.TestCase): """Tests for the issue https://github.com/python/cpython/issues/79516 - msgfmt.py shall accept multiple input files + msgfmt.py accepts multiple input files """ def test_no_outputfile(self): diff --git a/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst b/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst index 4df2309a1c3fd1..8d7cabef9064c3 100644 --- a/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst +++ b/Misc/NEWS.d/next/Tools-Demos/2018-12-05-20-46-10.bpo-35335.qtIUBx.rst @@ -1,2 +1,2 @@ -:program:`msgfmt.py` is now able to merge more than one single po file into a compiled mo +:program:`msgfmt.py` is now able to merge more than one po file into a compiled mo file. When an entry exists in more than on input file, the last file wins. diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py index da1e77a8d1fffa..d3b6ce67041bbb 100755 --- a/Tools/i18n/msgfmt.py +++ b/Tools/i18n/msgfmt.py @@ -6,9 +6,9 @@ This program converts a textual Uniforum-style message catalog (.po file) into a binary GNU catalog (.mo file). This is essentially the same function as the GNU msgfmt program, however, it is a simpler implementation. Currently it -handles plural forms and message contexts, but does not generate hash table. +handles plural forms and message contexts, but does not generate a hash table. -Usage: msgfmt.py [OPTIONS] filename.po [filename.po ...] +Usage: msgfmt.py [OPTIONS] filename.po ... Options: -o file @@ -105,7 +105,7 @@ def generate(messages): def make(filenames, outfile): - """ Compiles one or several po files(s). + """ Compiles one or more po files(s). filenames is a string or an iterable of strings representing input file(s) outfile is a string for the name of an input file or None. From 677f720c0540613d15d06356f7a9cafffe5c2e4a Mon Sep 17 00:00:00 2001 From: s-ball Date: Mon, 3 Mar 2025 23:46:32 +0100 Subject: [PATCH 20/36] Cosmetic improvements after review. PO test files are changed to the generic like other po files. --- Lib/test/test_tools/msgfmt_data/file12_fr.mo | Bin 566 -> 569 bytes .../test_tools/msgfmt_data/file1_fr_crlf.mo | Bin 486 -> 489 bytes .../test_tools/msgfmt_data/file1_fr_crlf.po | 11 ++++----- .../test_tools/msgfmt_data/file2_fr_lf.mo | Bin 473 -> 476 bytes .../test_tools/msgfmt_data/file2_fr_lf.po | 11 ++++----- Lib/test/test_tools/test_msgfmt.py | 2 ++ Tools/i18n/msgfmt.py | 22 ++++++++---------- 7 files changed, 20 insertions(+), 26 deletions(-) diff --git a/Lib/test/test_tools/msgfmt_data/file12_fr.mo b/Lib/test/test_tools/msgfmt_data/file12_fr.mo index 27100d8a4d1a959ef0f1fbf93708566d9a3eb30d..2a4c558b0ee639d4dd0d5fbe5baf58747e8f008d 100644 GIT binary patch delta 134 zcmdnSvXf+fV9%YOgH`jM`v$GcUOfl z*Pvief4_-4gY6?-9fNdz{dHX&6+HZ{d_A?J{QX?Hd=iUGbVG^~^NMp4OY)1X6x>36 ed=&f~eO(o7TzwrqeH36d=&f~eO(o7Tzwrq SeH)i3Rz^C8>IOsU?%;7*7KLm^2>( diff --git a/Lib/test/test_tools/msgfmt_data/file1_fr_crlf.po b/Lib/test/test_tools/msgfmt_data/file1_fr_crlf.po index ddb03784de9a70..148ace19db4bb2 100644 --- a/Lib/test/test_tools/msgfmt_data/file1_fr_crlf.po +++ b/Lib/test/test_tools/msgfmt_data/file1_fr_crlf.po @@ -1,15 +1,12 @@ -# French translations for python package. -# Copyright (C) 2018 THE python\'S COPYRIGHT HOLDER -# This file is distributed under the same license as the python package. -# s-ball , 2018. +# Example of French translations, crlf end of lines # msgid "" msgstr "" -"Project-Id-Version: python 3.8\n" +"Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-11-30 23:46+0100\n" -"PO-Revision-Date: 2018-11-30 23:47+0100\n" -"Last-Translator: s-ball \n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" "Language-Team: French\n" "Language: fr\n" "MIME-Version: 1.0\n" diff --git a/Lib/test/test_tools/msgfmt_data/file2_fr_lf.mo b/Lib/test/test_tools/msgfmt_data/file2_fr_lf.mo index c466a9b9acf2f9fda1b89952e78ea03708e2eba7..f85465b0d045e2b9c15b346ff025bdd8b76d8b7c 100644 GIT binary patch delta 118 zcmcb~e2000iD(=n14BFm0|PG*?*p>=f%KV)o?83?j?UhW?yd@9u0g?`{(ci{gY6?- z9fNdz{dHX&6+HZ{d_A?J{QX?Hd=iUGbVG^~^NMp4OY)1X6x>36d=&f~eO(o7Tzwrq SeHJ&9w~-4Gg$^5{pZ8Ly8jfigOZ6@{6n#igl9`b8-}HAhbhHVnKd!Nvd96 KYRO~~#uET*>mWb? diff --git a/Lib/test/test_tools/msgfmt_data/file2_fr_lf.po b/Lib/test/test_tools/msgfmt_data/file2_fr_lf.po index acd559492afe19..af8db8b3335f50 100644 --- a/Lib/test/test_tools/msgfmt_data/file2_fr_lf.po +++ b/Lib/test/test_tools/msgfmt_data/file2_fr_lf.po @@ -1,15 +1,12 @@ -# French translations for python package. -# Copyright (C) 2018 THE python'S COPYRIGHT HOLDER -# This file is distributed under the same license as the python package. -# s-ball , 2018. +# Example of French translations, lf end of lines # msgid "" msgstr "" -"Project-Id-Version: python 3.8\n" +"Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-11-30 23:57+0100\n" -"PO-Revision-Date: 2018-11-30 23:57+0100\n" -"Last-Translator: s-ball \n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" "Language-Team: French\n" "Language: fr\n" "MIME-Version: 1.0\n" diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index bfb6e8da102c08..7893032dd11b66 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -1,4 +1,5 @@ """Tests for the Tools/i18n/msgfmt.py tool.""" + import filecmp import os import shutil @@ -174,6 +175,7 @@ def update_catalog_snapshots(): for po_file in data_dir.glob('*.po'): mo_file = po_file.with_suffix('.mo') compile_messages(po_file, mo_file) + # cannot use compile_message because of both input files assert_python_ok(msgfmt, '-o', data_dir /'file12_fr.mo', data_dir / 'file1_fr_crlf.po', data_dir / 'file2_fr_lf.po') diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py index d3b6ce67041bbb..0984cb3e29377b 100755 --- a/Tools/i18n/msgfmt.py +++ b/Tools/i18n/msgfmt.py @@ -14,7 +14,13 @@ -o file --output-file=file Specify the output file to write to. If omitted, output will go to a - file named filename.mo (based off the input file name). + file named filename.mo (based off the input file name(s)). + If more than one input file is given, and if an output file is passed + with -o option, then all the input files are merged. If keys are + repeated (common for "" key for the header) the one from last file is used. + If more than one input file is given, and no -o option is present, then + every input file is compiled in its corresponding mo file (same name + with mo replacing po) -h --help @@ -23,14 +29,6 @@ -V --version Display version information and exit. - -If more than one input file is given, and if an output file is passed with --o option, then all the input files are merged. If keys are repeated (common -for "" key for the header) the one from last file is used. - -If more than one input file is given, and no -o option is present, then -every input file is compiled in its corresponding mo file (same name with mo -replacing po) """ import os @@ -255,9 +253,9 @@ def writefile(outfile, output): print(msg, file=sys.stderr) -def main(argv): +def main(): try: - opts, args = getopt.getopt(argv, 'hVo:', + opts, args = getopt.getopt(sys.argv[1:], 'hVo:', ['help', 'version', 'output-file=']) except getopt.error as msg: usage(1, msg) @@ -285,4 +283,4 @@ def main(argv): if __name__ == '__main__': - main(sys.argv[1:]) + main() From b4ea80a932afd011568702fd1ca64f33393d97fa Mon Sep 17 00:00:00 2001 From: s-ball Date: Tue, 4 Mar 2025 15:27:26 +0100 Subject: [PATCH 21/36] compile_messages now accepts several input files This allows to use it to rebuild file12_fr.mo from both file1_fr_crlf.po and file2_fr_lf.po. --- Lib/test/test_tools/test_msgfmt.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 7893032dd11b66..3d9375f781b8ca 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -20,8 +20,8 @@ msgfmt = script_dir / 'msgfmt.py' -def compile_messages(po_file, mo_file): - assert_python_ok(msgfmt, '-o', mo_file, po_file) +def compile_messages(mo_file, *po_files): + assert_python_ok(msgfmt, '-o', mo_file, *po_files) class CompilationTest(unittest.TestCase): @@ -36,7 +36,7 @@ def test_compilation(self): expected = GNUTranslations(f) tmp_mo_file = mo_file.name - compile_messages(po_file, tmp_mo_file) + compile_messages(tmp_mo_file, po_file) with open(tmp_mo_file, 'rb') as f: actual = GNUTranslations(f) @@ -174,9 +174,9 @@ def test_both_without_outputfile(self): def update_catalog_snapshots(): for po_file in data_dir.glob('*.po'): mo_file = po_file.with_suffix('.mo') - compile_messages(po_file, mo_file) - # cannot use compile_message because of both input files - assert_python_ok(msgfmt, '-o', data_dir /'file12_fr.mo', + compile_messages(mo_file, po_file) + # special processing for file12_fr.mo which results from 2 input files + compile_messages(data_dir /'file12_fr.mo', data_dir / 'file1_fr_crlf.po', data_dir / 'file2_fr_lf.po') From 3120add16ddb9682c26104f022db33068b23786f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89ric?= Date: Tue, 4 Mar 2025 11:19:05 -0500 Subject: [PATCH 22/36] whitespace nit --- Lib/test/test_tools/test_msgfmt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 3d9375f781b8ca..3ed6c14ab74226 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -160,8 +160,8 @@ def test_both_without_outputfile(self): """Test script without -o option and 2 input files""" with temp_cwd(None): - shutil.copy(data_dir /'file1_fr_crlf.po', '.') - shutil.copy(data_dir /'file2_fr_lf.po', '.') + shutil.copy(data_dir / 'file1_fr_crlf.po', '.') + shutil.copy(data_dir / 'file2_fr_lf.po', '.') assert_python_ok(msgfmt, 'file1_fr_crlf.po', 'file2_fr_lf.po') self.assertTrue( filecmp.cmp(data_dir / 'file1_fr_crlf.mo', 'file1_fr_crlf.mo'), @@ -176,7 +176,7 @@ def update_catalog_snapshots(): mo_file = po_file.with_suffix('.mo') compile_messages(mo_file, po_file) # special processing for file12_fr.mo which results from 2 input files - compile_messages(data_dir /'file12_fr.mo', + compile_messages(data_dir / 'file12_fr.mo', data_dir / 'file1_fr_crlf.po', data_dir / 'file2_fr_lf.po') From 9d91f12e7ddd8276fc6edad43c9711b0823242a5 Mon Sep 17 00:00:00 2001 From: s-ball Date: Tue, 4 Mar 2025 19:06:29 +0100 Subject: [PATCH 23/36] Make explicit how mo files can be re-generated. --- Lib/test/test_tools/test_msgfmt.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 3ed6c14ab74226..2e837628105aa9 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -1,4 +1,12 @@ -"""Tests for the Tools/i18n/msgfmt.py tool.""" +"""Tests for the Tools/i18n/msgfmt.py tool. + +Those tests use data files (po and mo) in the msgfmt_data folder. +The mo files can be generated (if the po file changes, or if msgfmt.py +slightly changes its output format) by using the --snapshot-update flag +with this script: + + python test_msgfmt.py --snapshot-update +""" import filecmp import os From 421272b34c6c9b692cc7573d888f93ca0c6dcc70 Mon Sep 17 00:00:00 2001 From: s-ball <43739830+s-ball@users.noreply.github.com> Date: Tue, 4 Mar 2025 19:12:01 +0100 Subject: [PATCH 24/36] Apply suggestions from code review Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> --- Lib/test/test_tools/test_msgfmt.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 2e837628105aa9..bd256ed7cebe8b 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -134,8 +134,7 @@ def test_nonexistent_file(self): class MultiInputTest(unittest.TestCase): - """Tests for the issue https://github.com/python/cpython/issues/79516 - msgfmt.py accepts multiple input files + """Tests for multiple input files """ def test_no_outputfile(self): @@ -161,8 +160,7 @@ def test_both_with_outputfile(self): data_dir / 'file1_fr_crlf.po', data_dir / 'file2_fr_lf.po') self.assertTrue( - filecmp.cmp(data_dir / 'file12_fr.mo', 'file12.mo'), - 'Wrong compiled file12.mo') + filecmp.cmp(data_dir / 'file12_fr.mo', 'file12.mo')) def test_both_without_outputfile(self): """Test script without -o option and 2 input files""" @@ -172,11 +170,9 @@ def test_both_without_outputfile(self): shutil.copy(data_dir / 'file2_fr_lf.po', '.') assert_python_ok(msgfmt, 'file1_fr_crlf.po', 'file2_fr_lf.po') self.assertTrue( - filecmp.cmp(data_dir / 'file1_fr_crlf.mo', 'file1_fr_crlf.mo'), - 'Wrong compiled file1_fr_crlf.mo') + filecmp.cmp(data_dir / 'file1_fr_crlf.mo', 'file1_fr_crlf.mo')) self.assertTrue( - filecmp.cmp(data_dir / 'file2_fr_lf.mo', 'file2_fr_lf.mo'), - 'Wrong compiled file2_fr_lf.mo') + filecmp.cmp(data_dir / 'file2_fr_lf.mo', 'file2_fr_lf.mo')) def update_catalog_snapshots(): From 09b97d9a9f8fb34fbe8637600fbea3be54106413 Mon Sep 17 00:00:00 2001 From: s-ball <43739830+s-ball@users.noreply.github.com> Date: Tue, 4 Mar 2025 19:13:05 +0100 Subject: [PATCH 25/36] Update Lib/test/test_tools/test_msgfmt.py Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> --- Lib/test/test_tools/test_msgfmt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index bd256ed7cebe8b..1460bcc588abc5 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -1,6 +1,6 @@ """Tests for the Tools/i18n/msgfmt.py tool. -Those tests use data files (po and mo) in the msgfmt_data folder. +These tests use data files (po and mo) in the msgfmt_data folder. The mo files can be generated (if the po file changes, or if msgfmt.py slightly changes its output format) by using the --snapshot-update flag with this script: From d45039c2a5cdc8fbdc366fa4c470f2c934e8223a Mon Sep 17 00:00:00 2001 From: s-ball Date: Sun, 16 Mar 2025 14:52:40 +0100 Subject: [PATCH 26/36] Generate json files for MultiInputTest data files. --- .../test_tools/msgfmt_data/file12_fr.json | 32 ++++++++++ .../test_tools/msgfmt_data/file1_fr_crlf.json | 24 +++++++ .../test_tools/msgfmt_data/file2_fr_lf.json | 14 +++++ Lib/test/test_tools/test_msgfmt.py | 63 ++++++++----------- 4 files changed, 95 insertions(+), 38 deletions(-) create mode 100644 Lib/test/test_tools/msgfmt_data/file12_fr.json create mode 100644 Lib/test/test_tools/msgfmt_data/file1_fr_crlf.json create mode 100644 Lib/test/test_tools/msgfmt_data/file2_fr_lf.json diff --git a/Lib/test/test_tools/msgfmt_data/file12_fr.json b/Lib/test/test_tools/msgfmt_data/file12_fr.json new file mode 100644 index 00000000000000..7df2f0c4c468d8 --- /dev/null +++ b/Lib/test/test_tools/msgfmt_data/file12_fr.json @@ -0,0 +1,32 @@ +[ + [ + "", + "Project-Id-Version: PACKAGE VERSION\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2018-11-30 23:57+0100\nPO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\nLast-Translator: FULL NAME \nLanguage-Team: French\nLanguage: fr\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=(n > 1);\n" + ], + [ + "Bye...", + "Au revoir ..." + ], + [ + "Hello!", + "Bonjour !" + ], + [ + "It's over.", + "C'est termin\u00e9." + ], + [ + [ + "{n} horse", + 0 + ], + "{n} cheval" + ], + [ + [ + "{n} horse", + 1 + ], + "{n} chevaux" + ] +] diff --git a/Lib/test/test_tools/msgfmt_data/file1_fr_crlf.json b/Lib/test/test_tools/msgfmt_data/file1_fr_crlf.json new file mode 100644 index 00000000000000..1cb8fccc66535a --- /dev/null +++ b/Lib/test/test_tools/msgfmt_data/file1_fr_crlf.json @@ -0,0 +1,24 @@ +[ + [ + "", + "Project-Id-Version: PACKAGE VERSION\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2018-11-30 23:46+0100\nPO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\nLast-Translator: FULL NAME \nLanguage-Team: French\nLanguage: fr\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=(n > 1);\n" + ], + [ + "Hello!", + "Bonjour !" + ], + [ + [ + "{n} horse", + 0 + ], + "{n} cheval" + ], + [ + [ + "{n} horse", + 1 + ], + "{n} chevaux" + ] +] diff --git a/Lib/test/test_tools/msgfmt_data/file2_fr_lf.json b/Lib/test/test_tools/msgfmt_data/file2_fr_lf.json new file mode 100644 index 00000000000000..e306d3f968825f --- /dev/null +++ b/Lib/test/test_tools/msgfmt_data/file2_fr_lf.json @@ -0,0 +1,14 @@ +[ + [ + "", + "Project-Id-Version: PACKAGE VERSION\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2018-11-30 23:57+0100\nPO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\nLast-Translator: FULL NAME \nLanguage-Team: French\nLanguage: fr\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=2; plural=(n > 1);\n" + ], + [ + "Bye...", + "Au revoir ..." + ], + [ + "It's over.", + "C'est termin\u00e9." + ] +] diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 1b2ff6fe2f0771..8c55f829b529e0 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -10,7 +10,6 @@ import filecmp import json -import os import shutil import sys import unittest @@ -21,7 +20,6 @@ from test.support.script_helper import assert_python_failure, assert_python_ok from test.test_tools import skip_if_missing, toolsdir - skip_if_missing('i18n') data_dir = (Path(__file__).parent / 'msgfmt_data').resolve() @@ -62,16 +60,10 @@ def test_translations(self): self.assertEqual(t.gettext('Multilinestring'), 'Multilinetranslation') self.assertEqual(t.gettext('"escapes"'), '"translated"') self.assertEqual(t.gettext('\n newlines \n'), '\n translated \n') - self.assertEqual(t.ngettext('One email sent.', '%d emails sent.', 1), - 'One email sent.') - self.assertEqual(t.ngettext('One email sent.', '%d emails sent.', 2), - '%d emails sent.') - self.assertEqual(t.npgettext('abc', 'One email sent.', - '%d emails sent.', 1), - 'One email sent.') - self.assertEqual(t.npgettext('abc', 'One email sent.', - '%d emails sent.', 2), - '%d emails sent.') + self.assertEqual(t.ngettext('One email sent.', '%d emails sent.', 1), 'One email sent.') + self.assertEqual(t.ngettext('One email sent.', '%d emails sent.', 2), '%d emails sent.') + self.assertEqual(t.npgettext('abc', 'One email sent.', '%d emails sent.', 1), 'One email sent.') + self.assertEqual(t.npgettext('abc', 'One email sent.', '%d emails sent.', 2), '%d emails sent.') def test_po_with_bom(self): with temp_cwd(): @@ -165,9 +157,7 @@ def test_no_outputfile(self): with temp_cwd(None): shutil.copy(data_dir / 'file2_fr_lf.po', '.') assert_python_ok(msgfmt, 'file2_fr_lf.po') - self.assertTrue( - filecmp.cmp(data_dir / 'file2_fr_lf.mo', 'file2_fr_lf.mo'), - 'Wrong compiled file2_fr_lf.mo') + self.assertTrue(filecmp.cmp(data_dir / 'file2_fr_lf.mo', 'file2_fr_lf.mo'), 'Wrong compiled file2_fr_lf.mo') def test_both_with_outputfile(self): """Test script with -o option and 2 input files @@ -179,11 +169,8 @@ def test_both_with_outputfile(self): Unix endings (lf) """ with temp_cwd(None): - assert_python_ok(msgfmt, '-o', 'file12.mo', - data_dir / 'file1_fr_crlf.po', - data_dir / 'file2_fr_lf.po') - self.assertTrue( - filecmp.cmp(data_dir / 'file12_fr.mo', 'file12.mo')) + assert_python_ok(msgfmt, '-o', 'file12.mo', data_dir / 'file1_fr_crlf.po', data_dir / 'file2_fr_lf.po') + self.assertTrue(filecmp.cmp(data_dir / 'file12_fr.mo', 'file12.mo')) def test_both_without_outputfile(self): """Test script without -o option and 2 input files""" @@ -192,30 +179,30 @@ def test_both_without_outputfile(self): shutil.copy(data_dir / 'file1_fr_crlf.po', '.') shutil.copy(data_dir / 'file2_fr_lf.po', '.') assert_python_ok(msgfmt, 'file1_fr_crlf.po', 'file2_fr_lf.po') - self.assertTrue( - filecmp.cmp(data_dir / 'file1_fr_crlf.mo', 'file1_fr_crlf.mo')) - self.assertTrue( - filecmp.cmp(data_dir / 'file2_fr_lf.mo', 'file2_fr_lf.mo')) + self.assertTrue(filecmp.cmp(data_dir / 'file1_fr_crlf.mo', 'file1_fr_crlf.mo')) + self.assertTrue(filecmp.cmp(data_dir / 'file2_fr_lf.mo', 'file2_fr_lf.mo')) + + +def make_message_files(mo_file, *po_files): + compile_messages(mo_file, *po_files) + # Create a human-readable JSON file which is + # easier to review than the binary .mo file. + with open(mo_file, 'rb') as f: + translations = GNUTranslations(f) + catalog_file = mo_file.with_suffix('.json') + with open(catalog_file, 'w') as f: + data = translations._catalog.items() + data = sorted(data, key=lambda x: (isinstance(x[0], tuple), x[0])) + json.dump(data, f, indent=4) + f.write('\n') def update_catalog_snapshots(): for po_file in data_dir.glob('*.po'): mo_file = po_file.with_suffix('.mo') - compile_messages(mo_file, po_file) - # Create a human-readable JSON file which is - # easier to review than the binary .mo file. - with open(mo_file, 'rb') as f: - translations = GNUTranslations(f) - catalog_file = po_file.with_suffix('.json') - with open(catalog_file, 'w') as f: - data = translations._catalog.items() - data = sorted(data, key=lambda x: (isinstance(x[0], tuple), x[0])) - json.dump(data, f, indent=4) - f.write('\n') + make_message_files(mo_file, po_file) # special processing for file12_fr.mo which results from 2 input files - compile_messages(data_dir / 'file12_fr.mo', - data_dir / 'file1_fr_crlf.po', - data_dir / 'file2_fr_lf.po') + make_message_files(data_dir / 'file12_fr.mo', data_dir / 'file1_fr_crlf.po', data_dir / 'file2_fr_lf.po') if __name__ == '__main__': From dde5ef16898210c2048221e0751270141603ad15 Mon Sep 17 00:00:00 2001 From: s-ball <43739830+s-ball@users.noreply.github.com> Date: Sun, 16 Mar 2025 19:06:54 +0100 Subject: [PATCH 27/36] Apply suggestions from code review Co-authored-by: Tomas R. --- Tools/i18n/msgfmt.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py index 8bb066dc0dad51..127f6aeb0c435c 100755 --- a/Tools/i18n/msgfmt.py +++ b/Tools/i18n/msgfmt.py @@ -17,9 +17,9 @@ file named filename.mo (based off the input file name(s)). If more than one input file is given, and if an output file is passed with -o option, then all the input files are merged. If keys are - repeated (common for "" key for the header) the one from last file is used. + repeated (common for "" key for the header) the one from the last file is used. If more than one input file is given, and no -o option is present, then - every input file is compiled in its corresponding mo file (same name + every input file is compiled to its corresponding mo file (same name with mo replacing po) -h @@ -102,10 +102,10 @@ def generate(messages): return output -def make(filenames, outfile): +def make(filenames, outfile=None): """ Compiles one or more po files(s). - filenames is a string or an iterable of strings representing input file(s) + filenames is a string or an iterable of strings representing input file(s). outfile is a string for the name of an input file or None. If it is not None, the output file receives a merge of the input files. From bb6e0c5de53147a0fa99bab7c10ec02862306098 Mon Sep 17 00:00:00 2001 From: s-ball Date: Sun, 16 Mar 2025 19:23:02 +0100 Subject: [PATCH 28/36] Cosmetic improvements (long lines...) --- Lib/test/test_tools/test_msgfmt.py | 33 +++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 8c55f829b529e0..2eed30c4ec7837 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -60,10 +60,14 @@ def test_translations(self): self.assertEqual(t.gettext('Multilinestring'), 'Multilinetranslation') self.assertEqual(t.gettext('"escapes"'), '"translated"') self.assertEqual(t.gettext('\n newlines \n'), '\n translated \n') - self.assertEqual(t.ngettext('One email sent.', '%d emails sent.', 1), 'One email sent.') - self.assertEqual(t.ngettext('One email sent.', '%d emails sent.', 2), '%d emails sent.') - self.assertEqual(t.npgettext('abc', 'One email sent.', '%d emails sent.', 1), 'One email sent.') - self.assertEqual(t.npgettext('abc', 'One email sent.', '%d emails sent.', 2), '%d emails sent.') + self.assertEqual(t.ngettext('One email sent.', '%d emails sent.', 1), + 'One email sent.') + self.assertEqual(t.ngettext('One email sent.', '%d emails sent.', 2), + '%d emails sent.') + self.assertEqual(t.npgettext('abc', 'One email sent.', + '%d emails sent.', 1), 'One email sent.') + self.assertEqual(t.npgettext('abc', 'One email sent.', + '%d emails sent.', 2), '%d emails sent.') def test_po_with_bom(self): with temp_cwd(): @@ -157,7 +161,9 @@ def test_no_outputfile(self): with temp_cwd(None): shutil.copy(data_dir / 'file2_fr_lf.po', '.') assert_python_ok(msgfmt, 'file2_fr_lf.po') - self.assertTrue(filecmp.cmp(data_dir / 'file2_fr_lf.mo', 'file2_fr_lf.mo'), 'Wrong compiled file2_fr_lf.mo') + self.assertTrue(filecmp.cmp( + data_dir / 'file2_fr_lf.mo','file2_fr_lf.mo'), + 'Wrong compiled file2_fr_lf.mo') def test_both_with_outputfile(self): """Test script with -o option and 2 input files @@ -169,8 +175,11 @@ def test_both_with_outputfile(self): Unix endings (lf) """ with temp_cwd(None): - assert_python_ok(msgfmt, '-o', 'file12.mo', data_dir / 'file1_fr_crlf.po', data_dir / 'file2_fr_lf.po') - self.assertTrue(filecmp.cmp(data_dir / 'file12_fr.mo', 'file12.mo')) + assert_python_ok(msgfmt, '-o', 'file12.mo', + data_dir / 'file1_fr_crlf.po', + data_dir / 'file2_fr_lf.po') + self.assertTrue(filecmp.cmp(data_dir / 'file12_fr.mo', + 'file12.mo')) def test_both_without_outputfile(self): """Test script without -o option and 2 input files""" @@ -179,8 +188,10 @@ def test_both_without_outputfile(self): shutil.copy(data_dir / 'file1_fr_crlf.po', '.') shutil.copy(data_dir / 'file2_fr_lf.po', '.') assert_python_ok(msgfmt, 'file1_fr_crlf.po', 'file2_fr_lf.po') - self.assertTrue(filecmp.cmp(data_dir / 'file1_fr_crlf.mo', 'file1_fr_crlf.mo')) - self.assertTrue(filecmp.cmp(data_dir / 'file2_fr_lf.mo', 'file2_fr_lf.mo')) + self.assertTrue(filecmp.cmp(data_dir / 'file1_fr_crlf.mo', + 'file1_fr_crlf.mo')) + self.assertTrue(filecmp.cmp(data_dir / 'file2_fr_lf.mo', + 'file2_fr_lf.mo')) def make_message_files(mo_file, *po_files): @@ -202,7 +213,9 @@ def update_catalog_snapshots(): mo_file = po_file.with_suffix('.mo') make_message_files(mo_file, po_file) # special processing for file12_fr.mo which results from 2 input files - make_message_files(data_dir / 'file12_fr.mo', data_dir / 'file1_fr_crlf.po', data_dir / 'file2_fr_lf.po') + make_message_files(data_dir / 'file12_fr.mo', + data_dir / 'file1_fr_crlf.po', + data_dir / 'file2_fr_lf.po') if __name__ == '__main__': From 905ec704a18bb80220aea823b054f8961f10c5ca Mon Sep 17 00:00:00 2001 From: s-ball Date: Mon, 17 Mar 2025 10:09:18 +0100 Subject: [PATCH 29/36] Revert an unwanted change. --- Lib/test/test_tools/test_msgfmt.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 2eed30c4ec7837..e23d9de1c2e957 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -65,9 +65,11 @@ def test_translations(self): self.assertEqual(t.ngettext('One email sent.', '%d emails sent.', 2), '%d emails sent.') self.assertEqual(t.npgettext('abc', 'One email sent.', - '%d emails sent.', 1), 'One email sent.') + '%d emails sent.', 1), + 'One email sent.') self.assertEqual(t.npgettext('abc', 'One email sent.', - '%d emails sent.', 2), '%d emails sent.') + '%d emails sent.', 2), + '%d emails sent.') def test_po_with_bom(self): with temp_cwd(): From b7a7c4865e709633e573987acf66c7749f81b5e6 Mon Sep 17 00:00:00 2001 From: s-ball Date: Tue, 18 Mar 2025 15:03:34 +0100 Subject: [PATCH 30/36] Simplifies the general logic of msgfmt.py All the logic is now in make at the cost of a break in compatibility. --- Tools/i18n/msgfmt.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py index 127f6aeb0c435c..e81a0cea5c137c 100755 --- a/Tools/i18n/msgfmt.py +++ b/Tools/i18n/msgfmt.py @@ -105,26 +105,31 @@ def generate(messages): def make(filenames, outfile=None): """ Compiles one or more po files(s). - filenames is a string or an iterable of strings representing input file(s). - outfile is a string for the name of an input file or None. + filenames an iterable of strings representing the input file(s). + outfile is a string for the name of an output file or None. If it is not None, the output file receives a merge of the input files. - If it is None, then filenames must be a string and the name of the output + If it is None, then for each file from filenames the name of the output file is obtained by replacing the po extension with mo. - Both ways are for compatibility reasons with previous behaviour. + BEWARE: in previous version of make the first parameter was a string + containing a single filename. """ - messages = {} - if isinstance(filenames, str): - infile, outfile = get_names(filenames, outfile) - process(infile, messages) - elif outfile is None: - raise TypeError("outfile cannot be None with more than one infile") + if outfile is None: + # each PO file generates its corresponding MO file + for filename in filenames: + messages = {} + infile, outfile = get_names(filename, None) + process(infile, messages) + output = generate(messages) + writefile(outfile, output) else: + # all PO files are combined into one single output file + messages = {} for filename in filenames: infile, _ = get_names(filename, outfile) process(infile, messages) - output = generate(messages) - writefile(outfile, output) + output = generate(messages) + writefile(outfile, output) def get_names(filename, outfile): # Compute .mo name from .po name and arguments @@ -276,11 +281,7 @@ def main(): print('No input file given', file=sys.stderr) print("Try `msgfmt --help' for more information.", file=sys.stderr) return - if outfile is None: - for filename in args: - make(filename, None) - else: - make(args, outfile) + make(args, outfile) if __name__ == '__main__': From 1b3b73e9b0b74df57a228819b9a5548f9c115218 Mon Sep 17 00:00:00 2001 From: s-ball <43739830+s-ball@users.noreply.github.com> Date: Tue, 18 Mar 2025 22:49:09 +0100 Subject: [PATCH 31/36] Update Tools/i18n/msgfmt.py Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> --- Tools/i18n/msgfmt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py index e81a0cea5c137c..a2b66cfc0d6198 100755 --- a/Tools/i18n/msgfmt.py +++ b/Tools/i18n/msgfmt.py @@ -105,7 +105,7 @@ def generate(messages): def make(filenames, outfile=None): """ Compiles one or more po files(s). - filenames an iterable of strings representing the input file(s). + filenames is an iterable of strings representing the input file(s). outfile is a string for the name of an output file or None. If it is not None, the output file receives a merge of the input files. From 1db2c66850383c38cbd155777d83d234a554748e Mon Sep 17 00:00:00 2001 From: s-ball Date: Tue, 18 Mar 2025 23:25:56 +0100 Subject: [PATCH 32/36] Changes per review. Remove one redundant test from test_msgfmt.py Remove unneeded changes from msgfmt.py --- Lib/test/test_tools/test_msgfmt.py | 9 --------- Tools/i18n/msgfmt.py | 16 +++++++++------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index e23d9de1c2e957..ae7d1643a8b2fa 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -158,15 +158,6 @@ class MultiInputTest(unittest.TestCase): """Tests for multiple input files """ - def test_no_outputfile(self): - """Test script without -o option - 1 single file""" - with temp_cwd(None): - shutil.copy(data_dir / 'file2_fr_lf.po', '.') - assert_python_ok(msgfmt, 'file2_fr_lf.po') - self.assertTrue(filecmp.cmp( - data_dir / 'file2_fr_lf.mo','file2_fr_lf.mo'), - 'Wrong compiled file2_fr_lf.mo') - def test_both_with_outputfile(self): """Test script with -o option and 2 input files diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py index e81a0cea5c137c..3eb9681d9f9c5f 100755 --- a/Tools/i18n/msgfmt.py +++ b/Tools/i18n/msgfmt.py @@ -102,7 +102,7 @@ def generate(messages): return output -def make(filenames, outfile=None): +def make(filenames, outfile): """ Compiles one or more po files(s). filenames an iterable of strings representing the input file(s). @@ -111,26 +111,24 @@ def make(filenames, outfile=None): If it is not None, the output file receives a merge of the input files. If it is None, then for each file from filenames the name of the output file is obtained by replacing the po extension with mo. - BEWARE: in previous version of make the first parameter was a string - containing a single filename. """ if outfile is None: # each PO file generates its corresponding MO file for filename in filenames: messages = {} - infile, outfile = get_names(filename, None) - process(infile, messages) + outfile = os.path.splitext(filename)[0] + '.mo' + process(filename, messages) output = generate(messages) writefile(outfile, output) else: # all PO files are combined into one single output file messages = {} for filename in filenames: - infile, _ = get_names(filename, outfile) - process(infile, messages) + process(filename, messages) output = generate(messages) writefile(outfile, output) + def get_names(filename, outfile): # Compute .mo name from .po name and arguments if filename.endswith('.po'): @@ -141,11 +139,15 @@ def get_names(filename, outfile): outfile = os.path.splitext(infile)[0] + '.mo' return infile, outfile + def process(infile, messages): ID = 1 STR = 2 CTXT = 3 + if not os.path.normcase(infile).endswith('.po'): + infile += '.po' + try: with open(infile, 'rb') as f: lines = f.readlines() From 743bdc59791637fff67b6f12473bc459424a2a4e Mon Sep 17 00:00:00 2001 From: s-ball Date: Wed, 19 Mar 2025 13:52:53 +0100 Subject: [PATCH 33/36] Removes the now unused get_names function. Fixes a small bug introduced in previous commit (changed behaviour when an input file has an extension other than .po) --- Tools/i18n/msgfmt.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/Tools/i18n/msgfmt.py b/Tools/i18n/msgfmt.py index 1d57de25d23f29..b6454e5b4dd4c9 100755 --- a/Tools/i18n/msgfmt.py +++ b/Tools/i18n/msgfmt.py @@ -116,8 +116,8 @@ def make(filenames, outfile): # each PO file generates its corresponding MO file for filename in filenames: messages = {} - outfile = os.path.splitext(filename)[0] + '.mo' - process(filename, messages) + infile = process(filename, messages) + outfile = os.path.splitext(infile)[0] + '.mo' output = generate(messages) writefile(outfile, output) else: @@ -129,18 +129,15 @@ def make(filenames, outfile): writefile(outfile, output) -def get_names(filename, outfile): - # Compute .mo name from .po name and arguments - if filename.endswith('.po'): - infile = filename - else: - infile = filename + '.po' - if outfile is None: - outfile = os.path.splitext(infile)[0] + '.mo' - return infile, outfile +def process(infile, messages): + """Extracts the translations from a PO file into a dict + Params: + infile: the path to a PO file - the .po extension is inferred if absent + messages: a dict that will be fed with the translations -def process(infile, messages): + Returns: the actual input file path with a .po extension + """ ID = 1 STR = 2 CTXT = 3 @@ -252,6 +249,7 @@ def process(infile, messages): # Add last entry if section == STR: add(msgctxt, msgid, msgstr, fuzzy, messages) + return infile def writefile(outfile, output): try: From 50e7145a4f14fe6b295d760f2b2996f3b9a14cba Mon Sep 17 00:00:00 2001 From: s-ball Date: Wed, 19 Mar 2025 18:06:27 +0100 Subject: [PATCH 34/36] Tests for extension corner cases for msgfmt.py --- Lib/test/test_tools/test_msgfmt.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index ae7d1643a8b2fa..712ceebb0a60e6 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -10,6 +10,7 @@ import filecmp import json +import os.path import shutil import sys import unittest @@ -187,6 +188,28 @@ def test_both_without_outputfile(self): 'file2_fr_lf.mo')) +class PONamesTest(unittest.TestCase): + def test_no_extension(self): + with temp_cwd(None): + shutil.copy(data_dir / 'file1_fr_crlf.po', 'file1.fr.po') + assert_python_ok(msgfmt, 'file1.fr') + self.assertTrue(os.path.exists('file1.fr.mo')) + + def test_wrong_extension(self): + with temp_cwd(None): + shutil.copy(data_dir / 'file1_fr_crlf.po', 'file1_fr.pox') + assert_python_failure(msgfmt, 'file1_fr.pox') + self.assertFalse(os.path.exists('file1_fr.mo')) + self.assertFalse(os.path.exists('file1_fr.pox.mo')) + + @unittest.skipUnless(sys.platform.startswith("win"), "uppercase on Windows") + def test_MAJ_on_Windows(self): + with temp_cwd(None): + shutil.copy(data_dir / 'file1_fr_crlf.po', 'File1.PO') + assert_python_ok(msgfmt, 'FIle1.Po') + self.assertTrue(os.path.exists('file1.mo')) + + def make_message_files(mo_file, *po_files): compile_messages(mo_file, *po_files) # Create a human-readable JSON file which is From a4f1769e7cb7735f6bd8358445c3298e39845aea Mon Sep 17 00:00:00 2001 From: s-ball Date: Fri, 4 Apr 2025 10:30:10 +0200 Subject: [PATCH 35/36] Fix a parameter inversion in test_msgfmt.py compile_messages parameters order has changed in a previous commit to allow compiling multiple PO files. --- Lib/test/test_tools/test_msgfmt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index 2b92133fba8a1a..2803a45747221a 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -54,7 +54,7 @@ def test_compilation(self): def test_binary_header(self): with temp_cwd(): tmp_mo_file = 'messages.mo' - compile_messages(data_dir / "general.po", tmp_mo_file) + compile_messages(tmp_mo_file, data_dir / "general.po") with open(tmp_mo_file, 'rb') as f: mo_data = f.read() From 4d54e50159329cc820d322cfd5c8834836142d54 Mon Sep 17 00:00:00 2001 From: s-ball Date: Thu, 22 May 2025 17:28:13 +0200 Subject: [PATCH 36/36] Merge main into multi_inputs Rename msgfmt to msgfmt_py accordingly with the changes in main --- Lib/test/test_tools/test_msgfmt.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_tools/test_msgfmt.py b/Lib/test/test_tools/test_msgfmt.py index dae1da9c18d795..407779ca110b78 100644 --- a/Lib/test/test_tools/test_msgfmt.py +++ b/Lib/test/test_tools/test_msgfmt.py @@ -33,7 +33,7 @@ def compile_many_messages(mo_file, *po_files): - assert_python_ok(msgfmt, '-o', mo_file, *po_files) + assert_python_ok(msgfmt_py, '-o', mo_file, *po_files) def compile_messages(po_file, mo_file): @@ -291,7 +291,7 @@ def test_both_with_outputfile(self): Unix endings (lf) """ with temp_cwd(None): - assert_python_ok(msgfmt, '-o', 'file12.mo', + assert_python_ok(msgfmt_py, '-o', 'file12.mo', data_dir / 'file1_fr_crlf.po', data_dir / 'file2_fr_lf.po') self.assertTrue(filecmp.cmp(data_dir / 'file12_fr.mo', @@ -303,7 +303,7 @@ def test_both_without_outputfile(self): with temp_cwd(None): shutil.copy(data_dir / 'file1_fr_crlf.po', '.') shutil.copy(data_dir / 'file2_fr_lf.po', '.') - assert_python_ok(msgfmt, 'file1_fr_crlf.po', 'file2_fr_lf.po') + assert_python_ok(msgfmt_py, 'file1_fr_crlf.po', 'file2_fr_lf.po') self.assertTrue(filecmp.cmp(data_dir / 'file1_fr_crlf.mo', 'file1_fr_crlf.mo')) self.assertTrue(filecmp.cmp(data_dir / 'file2_fr_lf.mo', @@ -314,13 +314,13 @@ class PONamesTest(unittest.TestCase): def test_no_extension(self): with temp_cwd(None): shutil.copy(data_dir / 'file1_fr_crlf.po', 'file1.fr.po') - assert_python_ok(msgfmt, 'file1.fr') + assert_python_ok(msgfmt_py, 'file1.fr') self.assertTrue(os.path.exists('file1.fr.mo')) def test_wrong_extension(self): with temp_cwd(None): shutil.copy(data_dir / 'file1_fr_crlf.po', 'file1_fr.pox') - assert_python_failure(msgfmt, 'file1_fr.pox') + assert_python_failure(msgfmt_py, 'file1_fr.pox') self.assertFalse(os.path.exists('file1_fr.mo')) self.assertFalse(os.path.exists('file1_fr.pox.mo')) @@ -328,7 +328,7 @@ def test_wrong_extension(self): def test_MAJ_on_Windows(self): with temp_cwd(None): shutil.copy(data_dir / 'file1_fr_crlf.po', 'File1.PO') - assert_python_ok(msgfmt, 'FIle1.Po') + assert_python_ok(msgfmt_py, 'FIle1.Po') self.assertTrue(os.path.exists('file1.mo'))