1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """Convert template files (like .pot or template .xlf files) translation files,
22 preserving existing translations.
23
24 See: http://translate.sourceforge.net/wiki/toolkit/pot2po for examples and
25 usage instructions.
26 """
27
28 from translate.storage import factory
29 from translate.search import match
30 from translate.misc.multistring import multistring
31 from translate.tools import pretranslate
32 from translate.storage import poheader, po
33 from translate.storage import catkeys
34
35
36 -def convertpot(input_file, output_file, template_file, tm=None, min_similarity=75, fuzzymatching=True, classes=factory.classes, **kwargs):
52
53
54 -def convert_stores(input_store, template_store, temp_store=None, tm=None, min_similarity=75, fuzzymatching=True, **kwargs):
55 """Actual conversion function, works on stores not files, returns
56 a properly initialized pretranslated output store, with structure
57 based on input_store, metadata based on template_store, migrates
58 old translations from template_store and pretranslating from tm"""
59
60 if temp_store is None:
61 temp_store = input_store
62
63
64 matchers = []
65 _prepare_merge(input_store, temp_store, template_store)
66 if fuzzymatching:
67 if template_store:
68 matcher = match.matcher(template_store, max_candidates=1, min_similarity=min_similarity, max_length=3000, usefuzzy=True)
69 matcher.addpercentage = False
70 matchers.append(matcher)
71 if tm:
72 matcher = pretranslate.memory(tm, max_candidates=1, min_similarity=min_similarity, max_length=1000)
73 matcher.addpercentage = False
74 matchers.append(matcher)
75
76
77 _store_pre_merge(input_store, temp_store, template_store)
78
79
80 match_locations = isinstance(input_store, po.pofile) and input_store.parseheader().get('X-Accelerator-Marker') in ('&', '~')
81 for input_unit in temp_store.units:
82 if input_unit.istranslatable():
83 input_unit = pretranslate.pretranslate_unit(input_unit, template_store, matchers, mark_reused=True, match_locations=match_locations)
84 _unit_post_merge(input_unit, input_store, temp_store, template_store)
85
86
87 _store_post_merge(input_store, temp_store, template_store)
88
89 return temp_store
90
91
92
93 -def _prepare_merge(input_store, output_store, template_store, **kwargs):
94 """Prepare stores & TM matchers before merging."""
95
96 prepare_merge_hook = "_prepare_merge_%s" % input_store.__class__.__name__
97 if prepare_merge_hook in globals():
98 globals()[prepare_merge_hook](input_store, output_store, template_store, **kwargs)
99
100
101 input_store.makeindex()
102 if template_store:
103 template_store.makeindex()
104
105
107 """Initialize the new file with things like headers and metadata."""
108
109 if isinstance(input_store, poheader.poheader):
110 _do_poheaders(input_store, output_store, template_store)
111 elif isinstance(input_store, catkeys.CatkeysFile):
112
113
114 output_store.header = input_store.header
115
116
117 store_pre_merge_hook = "_store_pre_merge_%s" % input_store.__class__.__name__
118 if store_pre_merge_hook in globals():
119 globals()[store_pre_merge_hook](input_store, output_store, template_store, **kwargs)
120
121 -def _store_post_merge(input_store, output_store, template_store, **kwargs):
122 """Close file after merging all translations, used for adding
123 statistics, obsolete messages and similar wrapup tasks."""
124
125 store_post_merge_hook = "_store_post_merge_%s" % input_store.__class__.__name__
126 if store_post_merge_hook in globals():
127 globals()[store_post_merge_hook](input_store, output_store, template_store, **kwargs)
128
129
130 -def _unit_post_merge(input_unit, input_store, output_store, template_store, **kwargs):
131 """Handle any unit level cleanup and situations not handled by the merge()
132 function."""
133
134 unit_post_merge_hook = "_unit_post_merge_%s" % input_unit.__class__.__name__
135 if unit_post_merge_hook in globals():
136 globals()[unit_post_merge_hook](input_unit, input_store, output_store, template_store, **kwargs)
137
138
139
141 """PO format specific template preparation logic."""
142
143
144 if template_store:
145 for unit in template_store.units:
146 if unit.isobsolete():
147 unit.resurrect()
148
149
150 -def _unit_post_merge_pounit(input_unit, input_store, output_store, template_store):
151 """PO format specific plural string initializtion logic."""
152
153 if input_unit.hasplural() and len(input_unit.target) == 0:
154
155 nplurals, plural = output_store.getheaderplural()
156 if nplurals and nplurals.isdigit() and nplurals != '2':
157 input_unit.target = multistring([""] * int(nplurals))
158
159
160 -def _store_post_merge_pofile(input_store, output_store, template_store):
161 """PO format specific: adds newly obsoleted messages to end of store."""
162
163 if template_store:
164 newlyobsoleted = []
165 for unit in template_store.units:
166 if not unit.istranslatable() or not unit.istranslated():
167 continue
168 if unit.target and not (input_store.findid(unit.getid()) or hasattr(unit, "reused")):
169
170 unit.makeobsolete()
171 newlyobsoleted.append(unit)
172 elif unit.isobsolete():
173 output_store.addunit(unit)
174 for unit in newlyobsoleted:
175 output_store.addunit(unit)
176
177
179 """Adds initialized PO headers to output store."""
180
181 charset = "UTF-8"
182 encoding = "8bit"
183 project_id_version = None
184 pot_creation_date = None
185 po_revision_date = None
186 last_translator = None
187 language_team = None
188 mime_version = None
189 plural_forms = None
190 kwargs = {}
191
192 if template_store is not None and isinstance(template_store, poheader.poheader):
193 templateheadervalues = template_store.parseheader()
194 for key, value in templateheadervalues.iteritems():
195 if key == "Project-Id-Version":
196 project_id_version = value
197 elif key == "Last-Translator":
198 last_translator = value
199 elif key == "Language-Team":
200 language_team = value
201 elif key == "PO-Revision-Date":
202 po_revision_date = value
203 elif key in ("POT-Creation-Date", "MIME-Version"):
204
205 pass
206 elif key == "Content-Type":
207 kwargs[key] = value
208 elif key == "Content-Transfer-Encoding":
209 encoding = value
210 elif key == "Plural-Forms":
211 plural_forms = value
212 else:
213 kwargs[key] = value
214
215 inputheadervalues = input_store.parseheader()
216 for key, value in inputheadervalues.iteritems():
217 if key in ("Project-Id-Version", "Last-Translator", "Language-Team", "PO-Revision-Date", "Content-Type", "Content-Transfer-Encoding", "Plural-Forms"):
218
219 pass
220 elif key == "POT-Creation-Date":
221 pot_creation_date = value
222 elif key == "MIME-Version":
223 mime_version = value
224 else:
225 kwargs[key] = value
226
227 output_header = output_store.init_headers(charset=charset, encoding=encoding, project_id_version=project_id_version,
228 pot_creation_date=pot_creation_date, po_revision_date=po_revision_date, last_translator=last_translator,
229 language_team=language_team, mime_version=mime_version, plural_forms=plural_forms, **kwargs)
230
231
232
233 if template_store is not None:
234 template_header = template_store.header()
235 if template_header is not None:
236 if template_header.getnotes("translator"):
237 output_header.addnote(template_header.getnotes("translator"), "translator", position="replace")
238 output_header.markfuzzy(template_header.isfuzzy())
239
240
241 -def main(argv=None):
242 from translate.convert import convert
243 formats = {"pot": ("po", convertpot), ("pot", "po"): ("po", convertpot),
244 "xlf": ("xlf", convertpot), ("xlf", "xlf"): ("xlf", convertpot),
245 "ts": ("ts", convertpot), ("ts", "ts"): ("ts", convertpot),
246 "catkeys": ("catkeys", convertpot), ("catkeys", "catkeys"): ("catkeys", convertpot),
247 }
248 parser = convert.ConvertOptionParser(formats, usepots=True, usetemplates=True,
249 allowmissingtemplate=True, description=__doc__)
250 parser.add_option("", "--tm", dest="tm", default=None,
251 help="The file to use as translation memory when fuzzy matching")
252 parser.passthrough.append("tm")
253 defaultsimilarity = 75
254 parser.add_option("-s", "--similarity", dest="min_similarity", default=defaultsimilarity,
255 type="float", help="The minimum similarity for inclusion (default: %d%%)" % defaultsimilarity)
256 parser.passthrough.append("min_similarity")
257 parser.add_option("--nofuzzymatching", dest="fuzzymatching", action="store_false",
258 default=True, help="Disable fuzzy matching")
259 parser.passthrough.append("fuzzymatching")
260 parser.run(argv)
261
262
263 if __name__ == '__main__':
264 main()
265