Merge lp:~songofacandy/bzr/i18n-msgfmt into lp:bzr
- i18n-msgfmt
- Merge into bzr.dev
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Martin Pool | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 5891 | ||||
Proposed branch: | lp:~songofacandy/bzr/i18n-msgfmt | ||||
Merge into: | lp:bzr | ||||
Prerequisite: | lp:~songofacandy/bzr/i18n-msgextract | ||||
Diff against target: |
363 lines (+326/-2) 3 files modified
setup.py (+11/-2) tools/build_mo.py (+107/-0) tools/msgfmt.py (+208/-0) |
||||
To merge this branch: | bzr merge lp:~songofacandy/bzr/i18n-msgfmt | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Martin Pool | Needs Information | ||
Review via email: mp+61082@code.launchpad.net |
This proposal supersedes a proposal from 2011-05-15.
Commit message
add build_mo command to setup.py
Description of the change
This merge proposal adds build_mo command to setup.py
This command builds bzrlib/
The directory format draft is described at http://
(resubmitted to set the Prerequisite branch)
methane (songofacandy) wrote : Posted in a previous version of this proposal | # |
John A Meinel (jameinel) wrote : Posted in a previous version of this proposal | # |
Do you know that you can set a Prerequisite branch as part of submitting the merge proposal? Changes in the prerequisite branch won't be shown in the diff. I think you can resubmit this and add the appropriate prereq. I'll try.
methane (songofacandy) wrote : Posted in a previous version of this proposal | # |
Oh, I didn't know that. Thank you.
On Mon, May 16, 2011 at 6:48 PM, John A Meinel <email address hidden> wrote:
> Do you know that you can set a Prerequisite branch as part of submitting the merge proposal? Changes in the prerequisite branch won't be shown in the diff. I think you can resubmit this and add the appropriate prereq. I'll try.
> --
> https:/
> You are the owner of lp:~songofacandy/bzr/i18n-msgfmt.
>
--
INADA Naoki <email address hidden>
Martin Pool (mbp) wrote : | # |
Sorry if I missed this in the list thread, but: can you tell me why we're adding a build_mo command, rather than using the external ones?
+ if 'en' in self.lang:
+ if find_executable
+ log.warn("GNU gettext msginit utility not found!")
+ log.warn("Skip creating English PO file.")
+ else:
+ log.info('Creating English PO file...')
+ pot = (self.prj_name or 'messages') + '.pot'
+ if self.prj_name:
+ en_po = '%s-en.po' % self.prj_name
+ else:
+ en_po = 'en.po'
+ self.spawn(
+ '--no-translator',
+ '-l', 'en',
+ '-i', os.path.
+ '-o', os.path.
+ ])
+
Could you explain this a bit more to me?
Thanks
methane (songofacandy) wrote : | # |
On Wed, May 18, 2011 at 12:01 AM, Martin Pool <email address hidden> wrote:
> Review: Needs Information
> Sorry if I missed this in the list thread, but: can you tell me why we're adding a build_mo command, rather than using the external ones?
setup.py command or Makefile command is needed with other tool, too.
So the problem is "msgfmt.py or external msgfmt command."
The advantage of msgfmt.py is that users don't need msgfmt command.
Users building bzr from source must have Python, of course.
But msgfmt tool is extra dependency for not only windows users, but also
Ubuntu users. Ubuntu doesn't installs msgfmt default and not all users
have root. Installing msgfmt is pain for non root users. Please imagine
"pip install --user bzr".
On the other hand, I don't imagine advantage of external msgfmt tool.
>
> + if 'en' in self.lang:
> + if find_executable
> + log.warn("GNU gettext msginit utility not found!")
> + log.warn("Skip creating English PO file.")
> + else:
> + log.info('Creating English PO file...')
> + pot = (self.prj_name or 'messages') + '.pot'
> + if self.prj_name:
> + en_po = '%s-en.po' % self.prj_name
> + else:
> + en_po = 'en.po'
> + self.spawn(
> + '--no-translator',
> + '-l', 'en',
> + '-i', os.path.
> + '-o', os.path.
> + ])
> +
>
> Could you explain this a bit more to me?
I've copied it from bzr-explorer and have didn't mind...
SImply, adding empty english po file is enough solution to remove
msginit dependency.
>
> Thanks
> --
> https:/
> You are the owner of lp:~songofacandy/bzr/i18n-msgfmt.
>
--
INADA Naoki <email address hidden>
Martin Pool (mbp) wrote : | # |
On 17 May 2011 18:49, INADA Naoki <email address hidden> wrote:
> On Wed, May 18, 2011 at 12:01 AM, Martin Pool <email address hidden> wrote:
>> Review: Needs Information
>> Sorry if I missed this in the list thread, but: can you tell me why we're adding a build_mo command, rather than using the external ones?
>
> setup.py command or Makefile command is needed with other tool, too.
> So the problem is "msgfmt.py or external msgfmt command."
>
> The advantage of msgfmt.py is that users don't need msgfmt command.
> Users building bzr from source must have Python, of course.
> But msgfmt tool is extra dependency for not only windows users, but also
> Ubuntu users. Ubuntu doesn't installs msgfmt default and not all users
> have root. Installing msgfmt is pain for non root users. Please imagine
> "pip install --user bzr".
>
> On the other hand, I don't imagine advantage of external msgfmt tool.
ok, that makes sense. thanks!
Martin Pool (mbp) wrote : | # |
sent to pqm by email
Preview Diff
1 | === modified file 'setup.py' | |||
2 | --- setup.py 2011-05-11 16:35:34 +0000 | |||
3 | +++ setup.py 2011-05-17 17:51:36 +0000 | |||
4 | @@ -69,7 +69,8 @@ | |||
5 | 69 | 'tests/ssl_certs/ca.crt', | 69 | 'tests/ssl_certs/ca.crt', |
6 | 70 | 'tests/ssl_certs/server_without_pass.key', | 70 | 'tests/ssl_certs/server_without_pass.key', |
7 | 71 | 'tests/ssl_certs/server_with_pass.key', | 71 | 'tests/ssl_certs/server_with_pass.key', |
9 | 72 | 'tests/ssl_certs/server.crt' | 72 | 'tests/ssl_certs/server.crt', |
10 | 73 | 'locale/*/LC_MESSAGES/*.mo', | ||
11 | 73 | ]}, | 74 | ]}, |
12 | 74 | } | 75 | } |
13 | 75 | 76 | ||
14 | @@ -152,6 +153,10 @@ | |||
15 | 152 | Generate bzr.1. | 153 | Generate bzr.1. |
16 | 153 | """ | 154 | """ |
17 | 154 | 155 | ||
18 | 156 | sub_commands = build.sub_commands + [ | ||
19 | 157 | ('build_mo', lambda _: True), | ||
20 | 158 | ] | ||
21 | 159 | |||
22 | 155 | def run(self): | 160 | def run(self): |
23 | 156 | build.run(self) | 161 | build.run(self) |
24 | 157 | 162 | ||
25 | @@ -163,8 +168,12 @@ | |||
26 | 163 | ## Setup | 168 | ## Setup |
27 | 164 | ######################## | 169 | ######################## |
28 | 165 | 170 | ||
29 | 171 | from tools.build_mo import build_mo | ||
30 | 172 | |||
31 | 166 | command_classes = {'install_scripts': my_install_scripts, | 173 | command_classes = {'install_scripts': my_install_scripts, |
33 | 167 | 'build': bzr_build} | 174 | 'build': bzr_build, |
34 | 175 | 'build_mo': build_mo, | ||
35 | 176 | } | ||
36 | 168 | from distutils import log | 177 | from distutils import log |
37 | 169 | from distutils.errors import CCompilerError, DistutilsPlatformError | 178 | from distutils.errors import CCompilerError, DistutilsPlatformError |
38 | 170 | from distutils.extension import Extension | 179 | from distutils.extension import Extension |
39 | 171 | 180 | ||
40 | === added file 'tools/build_mo.py' | |||
41 | --- tools/build_mo.py 1970-01-01 00:00:00 +0000 | |||
42 | +++ tools/build_mo.py 2011-05-17 17:51:36 +0000 | |||
43 | @@ -0,0 +1,107 @@ | |||
44 | 1 | # -*- coding: utf-8 -*- | ||
45 | 2 | # | ||
46 | 3 | # Copyright (C) 2007 Lukáš Lalinský <lalinsky@gmail.com> | ||
47 | 4 | # Copyright (C) 2007,2009 Alexander Belchenko <bialix@ukr.net> | ||
48 | 5 | # Copyright 2011 Canonical Ltd. | ||
49 | 6 | # | ||
50 | 7 | # This program is free software; you can redistribute it and/or | ||
51 | 8 | # modify it under the terms of the GNU General Public License | ||
52 | 9 | # as published by the Free Software Foundation; either version 2 | ||
53 | 10 | # of the License, or (at your option) any later version. | ||
54 | 11 | # | ||
55 | 12 | # This program is distributed in the hope that it will be useful, | ||
56 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
57 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
58 | 15 | # GNU General Public License for more details. | ||
59 | 16 | # | ||
60 | 17 | # You should have received a copy of the GNU General Public License | ||
61 | 18 | # along with this program; if not, write to the Free Software | ||
62 | 19 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
63 | 20 | |||
64 | 21 | # This code is bring from bzr-explorer and modified for bzr. | ||
65 | 22 | |||
66 | 23 | """build_mo command for setup.py""" | ||
67 | 24 | |||
68 | 25 | from distutils import log | ||
69 | 26 | from distutils.command.build import build | ||
70 | 27 | from distutils.core import Command | ||
71 | 28 | from distutils.dep_util import newer | ||
72 | 29 | from distutils.spawn import find_executable | ||
73 | 30 | import os | ||
74 | 31 | import re | ||
75 | 32 | |||
76 | 33 | import msgfmt | ||
77 | 34 | |||
78 | 35 | class build_mo(Command): | ||
79 | 36 | """Subcommand of build command: build_mo""" | ||
80 | 37 | |||
81 | 38 | description = 'compile po files to mo files' | ||
82 | 39 | |||
83 | 40 | # List of options: | ||
84 | 41 | # - long name, | ||
85 | 42 | # - short name (None if no short name), | ||
86 | 43 | # - help string. | ||
87 | 44 | user_options = [('build-dir=', 'd', 'Directory to build locale files'), | ||
88 | 45 | ('output-base=', 'o', 'mo-files base name'), | ||
89 | 46 | ('source-dir=', None, 'Directory with sources po files'), | ||
90 | 47 | ('force', 'f', 'Force creation of mo files'), | ||
91 | 48 | ('lang=', None, 'Comma-separated list of languages ' | ||
92 | 49 | 'to process'), | ||
93 | 50 | ] | ||
94 | 51 | |||
95 | 52 | boolean_options = ['force'] | ||
96 | 53 | |||
97 | 54 | def initialize_options(self): | ||
98 | 55 | self.build_dir = None | ||
99 | 56 | self.output_base = None | ||
100 | 57 | self.source_dir = None | ||
101 | 58 | self.force = None | ||
102 | 59 | self.lang = None | ||
103 | 60 | |||
104 | 61 | def finalize_options(self): | ||
105 | 62 | self.set_undefined_options('build', ('force', 'force')) | ||
106 | 63 | self.prj_name = self.distribution.get_name() | ||
107 | 64 | if self.build_dir is None: | ||
108 | 65 | self.build_dir = 'bzrlib/locale' | ||
109 | 66 | if not self.output_base: | ||
110 | 67 | self.output_base = self.prj_name or 'messages' | ||
111 | 68 | if self.source_dir is None: | ||
112 | 69 | self.source_dir = 'po' | ||
113 | 70 | if self.lang is None: | ||
114 | 71 | if self.prj_name: | ||
115 | 72 | re_po = re.compile(r'^(?:%s-)?([a-zA-Z_]+)\.po$' % self.prj_name) | ||
116 | 73 | else: | ||
117 | 74 | re_po = re.compile(r'^([a-zA-Z_]+)\.po$') | ||
118 | 75 | self.lang = [] | ||
119 | 76 | for i in os.listdir(self.source_dir): | ||
120 | 77 | mo = re_po.match(i) | ||
121 | 78 | if mo: | ||
122 | 79 | self.lang.append(mo.group(1)) | ||
123 | 80 | else: | ||
124 | 81 | self.lang = [i.strip() for i in self.lang.split(',') if i.strip()] | ||
125 | 82 | |||
126 | 83 | def run(self): | ||
127 | 84 | """Run msgfmt for each language""" | ||
128 | 85 | if not self.lang: | ||
129 | 86 | return | ||
130 | 87 | |||
131 | 88 | basename = self.output_base | ||
132 | 89 | if not basename.endswith('.mo'): | ||
133 | 90 | basename += '.mo' | ||
134 | 91 | |||
135 | 92 | po_prefix = '' | ||
136 | 93 | if self.prj_name: | ||
137 | 94 | po_prefix = self.prj_name + '-' | ||
138 | 95 | for lang in self.lang: | ||
139 | 96 | po = os.path.join('po', lang + '.po') | ||
140 | 97 | if not os.path.isfile(po): | ||
141 | 98 | po = os.path.join('po', po_prefix + lang + '.po') | ||
142 | 99 | dir_ = os.path.join(self.build_dir, lang, 'LC_MESSAGES') | ||
143 | 100 | self.mkpath(dir_) | ||
144 | 101 | mo = os.path.join(dir_, basename) | ||
145 | 102 | if self.force or newer(po, mo): | ||
146 | 103 | log.info('Compile: %s -> %s' % (po, mo)) | ||
147 | 104 | msgfmt.make(po, mo) | ||
148 | 105 | |||
149 | 106 | |||
150 | 107 | build.sub_commands.insert(0, ('build_mo', None)) | ||
151 | 0 | 108 | ||
152 | === added file 'tools/msgfmt.py' | |||
153 | --- tools/msgfmt.py 1970-01-01 00:00:00 +0000 | |||
154 | +++ tools/msgfmt.py 2011-05-17 17:51:36 +0000 | |||
155 | @@ -0,0 +1,208 @@ | |||
156 | 1 | #! /usr/bin/env python | ||
157 | 2 | # -*- coding: utf-8 -*- | ||
158 | 3 | # Written by Martin v. Lowis <loewis@informatik.hu-berlin.de> | ||
159 | 4 | |||
160 | 5 | # This script is copied from Python/Tools/i18n/msgfmt.py | ||
161 | 6 | # It licensed under PSF license which compatible with GPL. | ||
162 | 7 | # | ||
163 | 8 | # ChangeLog | ||
164 | 9 | # * Convert encoding from iso-8859-1 to utf-8 | ||
165 | 10 | # * Fix http://bugs.python.org/issue9741 | ||
166 | 11 | |||
167 | 12 | """Generate binary message catalog from textual translation description. | ||
168 | 13 | |||
169 | 14 | This program converts a textual Uniforum-style message catalog (.po file) into | ||
170 | 15 | a binary GNU catalog (.mo file). This is essentially the same function as the | ||
171 | 16 | GNU msgfmt program, however, it is a simpler implementation. | ||
172 | 17 | |||
173 | 18 | Usage: msgfmt.py [OPTIONS] filename.po | ||
174 | 19 | |||
175 | 20 | Options: | ||
176 | 21 | -o file | ||
177 | 22 | --output-file=file | ||
178 | 23 | Specify the output file to write to. If omitted, output will go to a | ||
179 | 24 | file named filename.mo (based off the input file name). | ||
180 | 25 | |||
181 | 26 | -h | ||
182 | 27 | --help | ||
183 | 28 | Print this message and exit. | ||
184 | 29 | |||
185 | 30 | -V | ||
186 | 31 | --version | ||
187 | 32 | Display version information and exit. | ||
188 | 33 | """ | ||
189 | 34 | |||
190 | 35 | import sys | ||
191 | 36 | import os | ||
192 | 37 | import getopt | ||
193 | 38 | import struct | ||
194 | 39 | import array | ||
195 | 40 | |||
196 | 41 | __version__ = "1.1" | ||
197 | 42 | |||
198 | 43 | MESSAGES = {} | ||
199 | 44 | |||
200 | 45 | |||
201 | 46 | def usage(code, msg=''): | ||
202 | 47 | print >> sys.stderr, __doc__ | ||
203 | 48 | if msg: | ||
204 | 49 | print >> sys.stderr, msg | ||
205 | 50 | sys.exit(code) | ||
206 | 51 | |||
207 | 52 | |||
208 | 53 | def add(id, str, fuzzy): | ||
209 | 54 | "Add a non-fuzzy translation to the dictionary." | ||
210 | 55 | global MESSAGES | ||
211 | 56 | if not fuzzy and str: | ||
212 | 57 | MESSAGES[id] = str | ||
213 | 58 | |||
214 | 59 | |||
215 | 60 | def generate(): | ||
216 | 61 | "Return the generated output." | ||
217 | 62 | global MESSAGES | ||
218 | 63 | keys = MESSAGES.keys() | ||
219 | 64 | # the keys are sorted in the .mo file | ||
220 | 65 | keys.sort() | ||
221 | 66 | offsets = [] | ||
222 | 67 | ids = strs = '' | ||
223 | 68 | for id in keys: | ||
224 | 69 | # For each string, we need size and file offset. Each string is NUL | ||
225 | 70 | # terminated; the NUL does not count into the size. | ||
226 | 71 | offsets.append((len(ids), len(id), len(strs), len(MESSAGES[id]))) | ||
227 | 72 | ids += id + '\0' | ||
228 | 73 | strs += MESSAGES[id] + '\0' | ||
229 | 74 | output = '' | ||
230 | 75 | # The header is 7 32-bit unsigned integers. We don't use hash tables, so | ||
231 | 76 | # the keys start right after the index tables. | ||
232 | 77 | # translated string. | ||
233 | 78 | keystart = 7*4+16*len(keys) | ||
234 | 79 | # and the values start after the keys | ||
235 | 80 | valuestart = keystart + len(ids) | ||
236 | 81 | koffsets = [] | ||
237 | 82 | voffsets = [] | ||
238 | 83 | # The string table first has the list of keys, then the list of values. | ||
239 | 84 | # Each entry has first the size of the string, then the file offset. | ||
240 | 85 | for o1, l1, o2, l2 in offsets: | ||
241 | 86 | koffsets += [l1, o1+keystart] | ||
242 | 87 | voffsets += [l2, o2+valuestart] | ||
243 | 88 | offsets = koffsets + voffsets | ||
244 | 89 | output = struct.pack("Iiiiiii", | ||
245 | 90 | 0x950412deL, # Magic | ||
246 | 91 | 0, # Version | ||
247 | 92 | len(keys), # # of entries | ||
248 | 93 | 7*4, # start of key index | ||
249 | 94 | 7*4+len(keys)*8, # start of value index | ||
250 | 95 | 0, 0) # size and offset of hash table | ||
251 | 96 | output += array.array("i", offsets).tostring() | ||
252 | 97 | output += ids | ||
253 | 98 | output += strs | ||
254 | 99 | return output | ||
255 | 100 | |||
256 | 101 | |||
257 | 102 | def make(filename, outfile): | ||
258 | 103 | # Fix http://bugs.python.org/issue9741 | ||
259 | 104 | global MESSAGES | ||
260 | 105 | MESSAGES.clear() | ||
261 | 106 | ID = 1 | ||
262 | 107 | STR = 2 | ||
263 | 108 | |||
264 | 109 | # Compute .mo name from .po name and arguments | ||
265 | 110 | if filename.endswith('.po'): | ||
266 | 111 | infile = filename | ||
267 | 112 | else: | ||
268 | 113 | infile = filename + '.po' | ||
269 | 114 | if outfile is None: | ||
270 | 115 | outfile = os.path.splitext(infile)[0] + '.mo' | ||
271 | 116 | |||
272 | 117 | try: | ||
273 | 118 | lines = open(infile).readlines() | ||
274 | 119 | except IOError, msg: | ||
275 | 120 | print >> sys.stderr, msg | ||
276 | 121 | sys.exit(1) | ||
277 | 122 | |||
278 | 123 | section = None | ||
279 | 124 | fuzzy = 0 | ||
280 | 125 | |||
281 | 126 | # Parse the catalog | ||
282 | 127 | lno = 0 | ||
283 | 128 | for l in lines: | ||
284 | 129 | lno += 1 | ||
285 | 130 | # If we get a comment line after a msgstr, this is a new entry | ||
286 | 131 | if l[0] == '#' and section == STR: | ||
287 | 132 | add(msgid, msgstr, fuzzy) | ||
288 | 133 | section = None | ||
289 | 134 | fuzzy = 0 | ||
290 | 135 | # Record a fuzzy mark | ||
291 | 136 | if l[:2] == '#,' and 'fuzzy' in l: | ||
292 | 137 | fuzzy = 1 | ||
293 | 138 | # Skip comments | ||
294 | 139 | if l[0] == '#': | ||
295 | 140 | continue | ||
296 | 141 | # Now we are in a msgid section, output previous section | ||
297 | 142 | if l.startswith('msgid'): | ||
298 | 143 | if section == STR: | ||
299 | 144 | add(msgid, msgstr, fuzzy) | ||
300 | 145 | section = ID | ||
301 | 146 | l = l[5:] | ||
302 | 147 | msgid = msgstr = '' | ||
303 | 148 | # Now we are in a msgstr section | ||
304 | 149 | elif l.startswith('msgstr'): | ||
305 | 150 | section = STR | ||
306 | 151 | l = l[6:] | ||
307 | 152 | # Skip empty lines | ||
308 | 153 | l = l.strip() | ||
309 | 154 | if not l: | ||
310 | 155 | continue | ||
311 | 156 | # XXX: Does this always follow Python escape semantics? | ||
312 | 157 | l = eval(l) | ||
313 | 158 | if section == ID: | ||
314 | 159 | msgid += l | ||
315 | 160 | elif section == STR: | ||
316 | 161 | msgstr += l | ||
317 | 162 | else: | ||
318 | 163 | print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \ | ||
319 | 164 | 'before:' | ||
320 | 165 | print >> sys.stderr, l | ||
321 | 166 | sys.exit(1) | ||
322 | 167 | # Add last entry | ||
323 | 168 | if section == STR: | ||
324 | 169 | add(msgid, msgstr, fuzzy) | ||
325 | 170 | |||
326 | 171 | # Compute output | ||
327 | 172 | output = generate() | ||
328 | 173 | |||
329 | 174 | try: | ||
330 | 175 | open(outfile,"wb").write(output) | ||
331 | 176 | except IOError,msg: | ||
332 | 177 | print >> sys.stderr, msg | ||
333 | 178 | |||
334 | 179 | |||
335 | 180 | def main(): | ||
336 | 181 | try: | ||
337 | 182 | opts, args = getopt.getopt(sys.argv[1:], 'hVo:', | ||
338 | 183 | ['help', 'version', 'output-file=']) | ||
339 | 184 | except getopt.error, msg: | ||
340 | 185 | usage(1, msg) | ||
341 | 186 | |||
342 | 187 | outfile = None | ||
343 | 188 | # parse options | ||
344 | 189 | for opt, arg in opts: | ||
345 | 190 | if opt in ('-h', '--help'): | ||
346 | 191 | usage(0) | ||
347 | 192 | elif opt in ('-V', '--version'): | ||
348 | 193 | print >> sys.stderr, "msgfmt.py", __version__ | ||
349 | 194 | sys.exit(0) | ||
350 | 195 | elif opt in ('-o', '--output-file'): | ||
351 | 196 | outfile = arg | ||
352 | 197 | # do it | ||
353 | 198 | if not args: | ||
354 | 199 | print >> sys.stderr, 'No input file given' | ||
355 | 200 | print >> sys.stderr, "Try `msgfmt --help' for more information." | ||
356 | 201 | return | ||
357 | 202 | |||
358 | 203 | for filename in args: | ||
359 | 204 | make(filename, outfile) | ||
360 | 205 | |||
361 | 206 | |||
362 | 207 | if __name__ == '__main__': | ||
363 | 208 | main() |
Uh, this mp is based on lp:~songofacandy/bzr/i18n-msgextract.
Extra diffs may be removed after that branch is merged.