-
Notifications
You must be signed in to change notification settings - Fork 459
Implementation of msgcat and msgmerge utilities from GNU gettext #1161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 13 commits
3ae7cbd
246671a
9a217e2
a1bf8d4
a5458fb
6020107
cb71c93
99ac987
7228cea
2dababc
888cdd0
5202291
efe2502
133c8db
4cbe604
8568e90
3f37414
1817bce
dd44348
0a6388d
5828c13
bbba96e
01f3793
1871bcf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -20,6 +20,7 @@ | |||||||
import sys | ||||||||
import tempfile | ||||||||
import warnings | ||||||||
from collections import OrderedDict, defaultdict | ||||||||
from configparser import RawConfigParser | ||||||||
from io import StringIO | ||||||||
from typing import BinaryIO, Iterable, Literal | ||||||||
|
@@ -852,6 +853,318 @@ def run(self): | |||||||
return | ||||||||
|
||||||||
|
||||||||
class ConcatenationCatalog(CommandMixin): | ||||||||
description = 'concatenates and merges the specified PO files' | ||||||||
user_options = [ | ||||||||
('input-files', None, 'input files'), | ||||||||
('files-from=', 'f', 'get list of input files from FILE'), | ||||||||
('directory=', 'D', 'add DIRECTORY to list for input files search' | ||||||||
'If input file is -, standard input is read.'), | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
('output-file=', 'o', 'write output to specified file'), | ||||||||
('less-than=', '<', 'print messages with less than this many' | ||||||||
'definitions, defaults to infinite if not set'), | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
('more-than=', '>', 'print messages with more than this many' | ||||||||
'definitions, defaults to 0 if not set'), | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
('unique', 'u', 'shorthand for --less-than=2, requests' | ||||||||
'that only unique messages be printed'), | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
('properties-input', 'P', 'input files are in Java .properties syntax'), | ||||||||
('stringtable-input', None, 'input files are in NeXTstep/GNUstep .strings syntax'), | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
('to-code=','t', 'encoding for output'), | ||||||||
('use-first', None, 'use first available translation for each' | ||||||||
'message, don\'t merge several translations'), | ||||||||
('lang=', None, 'set 'Language' field in the header entry'), | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
('color=', None, 'use colors and other text attributes always'), | ||||||||
('style=', None, 'specify CSS style rule file for --color'), | ||||||||
('no-escape', 'e', 'do not use C escapes in output (default)'), | ||||||||
('escape', 'E', 'use C escapes in output, no extended chars'), | ||||||||
('force-po', None, 'write PO file even if empty'), | ||||||||
('indent', 'i', 'write the .po file using indented style'), | ||||||||
('no-location', None, 'do not write \'#: filename:line\' lines'), | ||||||||
('strict', None, 'write out strict Uniforum conforming .po file'), | ||||||||
('properties-output', None, 'write out a Java .properties file'), | ||||||||
('stringtable-output', None, 'write out a NeXTstep/GNUstep .strings file'), | ||||||||
('width=', 'w', 'set output page width'), | ||||||||
('no-wrap', None, 'do not break long message lines, longer than' | ||||||||
'the output page width, into several lines'), | ||||||||
('sort-output', 's', 'generate sorted output'), | ||||||||
('sort-by-file', 'F', 'sort output by file location'), | ||||||||
] | ||||||||
|
||||||||
as_args='input-files' | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
boolean_options = [ | ||||||||
'unique', | ||||||||
'properties-input', | ||||||||
'stringtable-input', | ||||||||
'no-escape', | ||||||||
'escape', | ||||||||
'force-po', | ||||||||
'indent', | ||||||||
'no-location', | ||||||||
'strict', | ||||||||
'properties-output', | ||||||||
'stringtable-output', | ||||||||
'no-wrap', | ||||||||
'sort-output', | ||||||||
'sort-by-file', | ||||||||
] | ||||||||
|
||||||||
option_choices = { | ||||||||
'color': ('always', 'never', 'auto', 'html'), | ||||||||
} | ||||||||
|
||||||||
def initialize_options(self): | ||||||||
self.input_files = None # | ||||||||
self.files_from = None | ||||||||
self.directory = None | ||||||||
self.output_file = None # | ||||||||
self.less_than = None # | ||||||||
self.more_than = 0 # | ||||||||
self.unique = False # | ||||||||
self.properties_input = None | ||||||||
self.stringtable_input = None | ||||||||
self.to_code = None | ||||||||
# the first translation is always used temporarily | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
self.use_first = True #~ | ||||||||
self.lang = None | ||||||||
self.color = None | ||||||||
self.style = None | ||||||||
self.no_escape = None | ||||||||
self.escape = None | ||||||||
self.force_po = None | ||||||||
self.indent = None | ||||||||
self.no_location = None # | ||||||||
self.strict = None | ||||||||
self.properties_output = None | ||||||||
self.stringtable_output = None | ||||||||
self.width = None # | ||||||||
self.no_wrap = None # | ||||||||
self.sort_output = False # | ||||||||
self.sort_by_file = False # | ||||||||
|
||||||||
def finalize_options(self): | ||||||||
if not self.input_files: | ||||||||
raise OptionError('you must specify the input files') | ||||||||
if not self.output_file: | ||||||||
raise OptionError('you must specify the output file') | ||||||||
Comment on lines
+895
to
+896
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this not output to stdout? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you want the result to be output to stdout or what? It's just that each command in frontend has a similar exception for the output_file There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I meant that it would probably be useful for this command to be able to output to stdout (if no specific output file is set).
|
||||||||
|
||||||||
if self.no_wrap and self.width: | ||||||||
raise OptionError("'--no-wrap' and '--width' are mutually exclusive") | ||||||||
if not self.no_wrap and not self.width: | ||||||||
self.width = 76 | ||||||||
elif self.width is not None: | ||||||||
self.width = int(self.width) | ||||||||
|
||||||||
if self.more_than is None: | ||||||||
self.more_than = 0 | ||||||||
else: | ||||||||
self.more_than = int(self.more_than) | ||||||||
if self.less_than is not None: | ||||||||
self.less_than = int(self.less_than) | ||||||||
if self.unique: | ||||||||
self.less_than = 2 | ||||||||
Comment on lines
+911
to
+912
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not quite so. More_than and less_than are like bounds. Unique means less_than=2 but boolean There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mean... does it make sense to be able to set |
||||||||
|
||||||||
def _prepare(self): | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to do a bit more than just preparation... maybe a better name? |
||||||||
self.message_count = defaultdict(int) | ||||||||
|
||||||||
for filename in self.input_files: | ||||||||
with open(filename, 'r') as pofile: | ||||||||
template = read_po(pofile) | ||||||||
for message in template: | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably also filter out messages with a nullish |
||||||||
self.message_count[message.id] += 1 | ||||||||
|
||||||||
def run(self): | ||||||||
catalog = Catalog(fuzzy=False) | ||||||||
self._prepare() | ||||||||
|
||||||||
for filename in self.input_files: | ||||||||
with open(filename, 'r') as pofile: | ||||||||
template = read_po(pofile) | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
if catalog.locale is None: | ||||||||
catalog.locale = template.locale | ||||||||
|
||||||||
for message in template: | ||||||||
if not message.id: | ||||||||
continue | ||||||||
|
||||||||
if message.id in catalog and catalog[message.id].string != message.string and not self.use_first: | ||||||||
raise NotImplementedError() | ||||||||
|
||||||||
message_count = self.message_count[message.id] | ||||||||
if message_count > self.more_than and (self.less_than is None or message_count < self.less_than): | ||||||||
catalog[message.id] = message | ||||||||
|
||||||||
catalog.fuzzy = any(message.fuzzy for message in catalog) | ||||||||
with open(self.output_file, 'wb') as outfile: | ||||||||
write_po( | ||||||||
outfile, | ||||||||
catalog, | ||||||||
width=self.width, | ||||||||
sort_by_file=self.sort_by_file, | ||||||||
sort_output=self.sort_output, | ||||||||
no_location=self.no_location, | ||||||||
) | ||||||||
|
||||||||
|
||||||||
class MergeCatalog(CommandMixin): | ||||||||
description='combines two Uniforum-style PO files into one' | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
user_options=[ | ||||||||
('input-files', None, 'def.po ref.pot'), | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
('directory=', 'D', 'add DIRECTORY to list for input files search'), | ||||||||
('compendium=', 'C', 'additional library of message translations, may be specified more than once'), | ||||||||
akx marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
('compendium-overwrite', '', 'overwrite mode of compendium'), | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This help text doesn't really help much... 😄 |
||||||||
('no-compendium-comment', '', ''), | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this have a help text? |
||||||||
('update', 'U', 'pdate def.po, do nothing if def.po already up to date'), | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
('output-file=', 'o', 'write output to specified file, the results are written' | ||||||||
'to standard output if no output file is specified'), | ||||||||
('backup', None, 'make a backup of def.po'), | ||||||||
('suffix=', None, 'override the usual backup suffix'), | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What "usual backup suffix"? 🤔 |
||||||||
('multi-domain', 'm', 'apply ref.pot to each of the domains in def.po'), | ||||||||
('for-msgfmt', None, 'produce output for 'msgfmt', not for a translator'), | ||||||||
('no-fuzzy-matching', 'N', 'do not use fuzzy matching'), | ||||||||
('previous', None, 'keep previous msgids of translated messages'), | ||||||||
('properties-input', 'P', 'input files are in Java .properties syntax'), | ||||||||
('stringtable-input', None, 'input files are in NeXTstep/GNUstep .strings syntax'), | ||||||||
('lang=', None, 'set 'Language' field in the header entry'), | ||||||||
('color=', None, 'use colors and other text attributes always'), | ||||||||
('style=', None, 'specify CSS style rule file for --color'), | ||||||||
('no-escape', 'e', 'do not use C escapes in output (default)'), | ||||||||
('escape', 'E', 'use C escapes in output, no extended chars'), | ||||||||
('force-po', None, 'write PO file even if empty'), | ||||||||
('indent', 'i', 'indented output style'), | ||||||||
('no-location', None, 'suppress \'#: filename:line\' lines'), | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's use the same text as in the other tools'
Suggested change
|
||||||||
('strict', None, 'strict Uniforum output style'), | ||||||||
('properties-output', None, 'write out a Java .properties file'), | ||||||||
('stringtable-output', None, 'write out a NeXTstep/GNUstep .strings file'), | ||||||||
('width=', 'w', 'set output page width'), | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's use the same text as in the other tools' width option:
Suggested change
|
||||||||
('no-wrap', None, 'do not break long message lines, longer' | ||||||||
'than the output page width, into several lines'), | ||||||||
('sort-output', 's', 'generate sorted output'), | ||||||||
('sort-by-file', 'F', 'sort output by file location'), | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
] | ||||||||
|
||||||||
as_args='input-files' | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
|
||||||||
boolean_options = [ | ||||||||
'update', | ||||||||
'multi-domain', | ||||||||
'for-msgfmt', | ||||||||
'no-fuzzy-matching', | ||||||||
'previous' | ||||||||
'properties-input', | ||||||||
'stringtable-input', | ||||||||
'no-escape', | ||||||||
'escape', | ||||||||
'force-po', | ||||||||
'indent', | ||||||||
'no-location', | ||||||||
'strict', | ||||||||
'properties-output', | ||||||||
'stringtable-output', | ||||||||
'no-wrap', | ||||||||
'sort-output', | ||||||||
'sort-by-file', | ||||||||
'compendium-overwrite', | ||||||||
'backup', | ||||||||
'no-compendium-comment', | ||||||||
] | ||||||||
|
||||||||
option_choices = { | ||||||||
'color': ('always', 'never', 'auto', 'html'), | ||||||||
} | ||||||||
|
||||||||
def initialize_options(self): | ||||||||
self.input_files = None # | ||||||||
self.directory = None | ||||||||
|
||||||||
self.compendium = None #~ | ||||||||
self.compendium_overwrite = False # | ||||||||
self.no_compendium_comment = None # | ||||||||
|
||||||||
self.update = None # | ||||||||
self.output_file = None # | ||||||||
self.backup = False # | ||||||||
self.suffix = '~' # | ||||||||
self.multi_domain = None | ||||||||
self.for_msgfmt = None | ||||||||
self.no_fuzzy_matching = None # | ||||||||
self.previous = None | ||||||||
self.properties_input = None | ||||||||
self.stringtable_input = None | ||||||||
self.lang = None | ||||||||
self.color = None | ||||||||
self.style = None | ||||||||
self.no_escape = None | ||||||||
self.escape = None | ||||||||
self.force_po = None | ||||||||
self.indent = None | ||||||||
self.no_location = None # | ||||||||
self.strict = None | ||||||||
self.properties_output = None | ||||||||
self.stringtable_output = None | ||||||||
self.width = None # | ||||||||
self.no_wrap = None # | ||||||||
self.sort_output = False # | ||||||||
self.sort_by_file = False # | ||||||||
|
||||||||
def finalize_options(self): | ||||||||
if not self.input_files or len(self.input_files) != 2: | ||||||||
raise OptionError('must be two po files') | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
if not self.output_file and not self.update: | ||||||||
raise OptionError('you must specify the output file or update existing') | ||||||||
|
||||||||
if self.no_wrap and self.width: | ||||||||
raise OptionError("'--no-wrap' and '--width' are mutually exclusive") | ||||||||
if not self.no_wrap and not self.width: | ||||||||
self.width = 76 | ||||||||
elif self.width is not None: | ||||||||
self.width = int(self.width) | ||||||||
|
||||||||
def run(self): | ||||||||
def_file, ref_file = self.input_files | ||||||||
|
||||||||
if self.update and self.backup: | ||||||||
shutil.copy(def_file, def_file + self.suffix) | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
|
||||||||
with open(def_file, 'r') as pofile: | ||||||||
catalog = read_po(pofile) | ||||||||
with open(ref_file, 'r') as pofile: | ||||||||
ref_catalog = read_po(pofile) | ||||||||
catalog.update( | ||||||||
ref_catalog, | ||||||||
no_fuzzy_matching=self.no_fuzzy_matching | ||||||||
) | ||||||||
|
||||||||
if self.compendium: | ||||||||
with open(self.compendium, 'r') as pofile: | ||||||||
compendium_catalog = read_po(pofile) | ||||||||
|
||||||||
for message in compendium_catalog: | ||||||||
current = catalog[message.id] | ||||||||
if message.id in catalog and (not current.string or current.fuzzy or self.compendium_overwrite): | ||||||||
Comment on lines
+1058
to
+1059
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good place to use the walrus operator – the unlearned reader doesn't know that
Suggested change
|
||||||||
if self.compendium_overwrite and not current.fuzzy and current.string: | ||||||||
catalog.obsolete[message.id] = current.clone() | ||||||||
|
||||||||
current.string = message.string | ||||||||
if current.fuzzy: | ||||||||
current.flags.remove('fuzzy') | ||||||||
|
||||||||
if not self.no_compendium_comment: | ||||||||
current.auto_comments.append(self.compendium) | ||||||||
|
||||||||
catalog.fuzzy = any(message.fuzzy for message in catalog) | ||||||||
output_path = def_file if self.update else self.output_file | ||||||||
with open(output_path, 'wb') as outfile: | ||||||||
write_po( | ||||||||
outfile, | ||||||||
catalog, | ||||||||
no_location=self.no_location, | ||||||||
width=self.width, | ||||||||
sort_by_file=self.sort_by_file, | ||||||||
sort_output=self.sort_output, | ||||||||
) | ||||||||
|
||||||||
|
||||||||
class CommandLineInterface: | ||||||||
"""Command-line interface. | ||||||||
|
||||||||
|
@@ -866,13 +1179,17 @@ class CommandLineInterface: | |||||||
'extract': 'extract messages from source files and generate a POT file', | ||||||||
'init': 'create new message catalogs from a POT file', | ||||||||
'update': 'update existing message catalogs from a POT file', | ||||||||
'concat': 'concatenates and merges the specified PO files', | ||||||||
'merge': 'combines two Uniforum-style PO files into one', | ||||||||
soft-suroleb marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
} | ||||||||
|
||||||||
command_classes = { | ||||||||
'compile': CompileCatalog, | ||||||||
'extract': ExtractMessages, | ||||||||
'init': InitCatalog, | ||||||||
'update': UpdateCatalog, | ||||||||
'concat': ConcatenationCatalog, | ||||||||
'merge': MergeCatalog, | ||||||||
} | ||||||||
|
||||||||
log = None # Replaced on instance level | ||||||||
|
Uh oh!
There was an error while loading. Please reload this page.