From 4cb2f4102ca18b7c48029b3d8b6ca55f8dc56fbb Mon Sep 17 00:00:00 2001 From: harshitgupta31415 Date: Sun, 11 Oct 2026 15:37:20 +0530 Subject: [PATCH] mofile: avoid repeated string-table copies Use separate bytearray buffers for message ids and translations instead of rebuilding bytes objects for each entry. Keep the same offsets and final bytes write. Add tests for encodings, contexts, plural forms, fuzzy entries and writing the output once. --- babel/messages/mofile.py | 3 +- tests/messages/test_mofile.py | 54 +++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/babel/messages/mofile.py b/babel/messages/mofile.py index 2413f7139..164d0b41b 100644 --- a/babel/messages/mofile.py +++ b/babel/messages/mofile.py @@ -163,7 +163,8 @@ def write_mo(fileobj: SupportsWrite[bytes], catalog: Catalog, use_fuzzy: bool = messages[1:] = [m for m in messages[1:] if m.string and (use_fuzzy or not m.fuzzy)] messages.sort() - ids = strs = b'' + ids = bytearray() + strs = bytearray() offsets = [] for message in messages: diff --git a/tests/messages/test_mofile.py b/tests/messages/test_mofile.py index e572931c2..84b6f431d 100644 --- a/tests/messages/test_mofile.py +++ b/tests/messages/test_mofile.py @@ -11,8 +11,11 @@ # history and logs, available at https://github.com/python-babel/babel/commits/master/. import os +import struct from io import BytesIO +import pytest + from babel.messages import Catalog, mofile from babel.support import Translations @@ -104,3 +107,54 @@ def test_read_mo_decodes_message_context() -> None: assert message.context == 'ほげ' assert message.string == 'åäöÅÄÖ' assert not catalog.get('x') # Not without the context + + +@pytest.mark.parametrize('charset', ['utf-8', 'iso-8859-1']) +@pytest.mark.parametrize('use_fuzzy', [False, True]) +def test_write_mo_string_tables(charset, use_fuzzy): + catalog = Catalog(locale='fr', charset=charset) + catalog.add('Coffee', 'Café') + catalog.add(('One file', 'Many files'), ('Un fichier', 'Des fichiers')) + catalog.add('Open', 'Ouvrir', context='menu') + catalog.add('Draft', 'Brouillon', flags=['fuzzy']) + catalog.add('Untranslated', '') + output = BytesIO() + + mofile.write_mo(output, catalog, use_fuzzy=use_fuzzy) + + data = output.getvalue() + _, _, count, key_table, value_table, _, _ = struct.unpack_from('Iiiiiii', data) + entries = {} + for index in range(count): + key_size, key_offset = struct.unpack_from('ii', data, key_table + 8 * index) + value_size, value_offset = struct.unpack_from('ii', data, value_table + 8 * index) + assert data[key_offset + key_size] == 0 + assert data[value_offset + value_size] == 0 + key = data[key_offset:key_offset + key_size].decode(charset) + value = data[value_offset:value_offset + value_size].decode(charset) + entries[key] = value + del entries[''] + expected = { + 'Coffee': 'Café', + 'One file\x00Many files': 'Un fichier\x00Des fichiers', + 'menu\x04Open': 'Ouvrir', + } + if use_fuzzy: + expected['Draft'] = 'Brouillon' + assert entries == expected + + +def test_write_mo_writes_bytes_once(): + catalog = Catalog() + catalog.add('Message', 'Translation') + writes = [] + + class Output: + def write(self, data): + assert type(data) is bytes + writes.append(data) + + mofile.write_mo(Output(), catalog) + + assert len(writes) == 1 + assert mofile.read_mo(BytesIO(writes[0]))['Message'].string == 'Translation'