]> git.lyx.org Git - lyx.git/blob - po/lyx_pot.py
Move Lexer to support/ directory (and lyx::support namespace)
[lyx.git] / po / lyx_pot.py
1 #!/usr/bin/python3
2 # -*- coding: utf-8 -*-
3
4 # file lyx_pot.py
5 # This file is part of LyX, the document processor.
6 # Licence details can be found in the file COPYING.
7 #
8 # \author Bo Peng
9 #
10 # Full author contact details are available in file CREDITS
11
12 # Usage: use
13 #     lyx_pot.py -h
14 # to get usage message
15
16 # This script will extract translatable strings from input files and write
17 # to output in gettext .pot format.
18 #
19 from __future__ import print_function
20
21 import glob, sys, os, re, getopt
22 import io
23
24 def relativePath(path, base):
25     '''return relative path from top source dir'''
26     # full pathname of path
27     path1 = os.path.normpath(os.path.realpath(path)).split(os.sep)
28     path2 = os.path.normpath(os.path.realpath(base)).split(os.sep)
29     if path1[:len(path2)] != path2:
30         print("Path %s is not under top source directory" % path)
31     path3 = os.path.join(*path1[len(path2):]);
32     # replace all \ by / such that we get the same comments on Windows and *nix
33     path3 = path3.replace('\\', '/')
34     return path3
35
36
37 def writeString(outfile, infile, basefile, lineno, string):
38     string = string.replace('\\', '\\\\').replace('"', '')
39     if string == "":
40         return
41     print(u'#: %s:%d\nmsgid "%s"\nmsgstr ""\n' % \
42         (relativePath(infile, basefile), lineno, string), file=outfile)
43
44
45 def ui_l10n(input_files, output, base):
46     '''Generate pot file from lib/ui/*'''
47     output = io.open(output, 'w', encoding='utf_8', newline='\n')
48     Submenu = re.compile(r'^[^#]*Submenu\s+"([^"]*)"', re.IGNORECASE)
49     Popupmenu = re.compile(r'^[^#]*PopupMenu\s+"[^"]+"\s+"([^"]*)"', re.IGNORECASE)
50     Dynamicmenu = re.compile(r'^[^#]*DynamicMenu\s+"[^"]+"\s+"([^"]*)"', re.IGNORECASE)
51     IconPalette = re.compile(r'^[^#]*IconPalette\s+"[^"]+"\s+"([^"]*)"', re.IGNORECASE)
52     Toolbar = re.compile(r'^[^#]*Toolbar\s+"[^"]+"\s+"([^"]*)"', re.IGNORECASE)
53     Item = re.compile(r'[^#]*Item\s+"([^"]*)"', re.IGNORECASE)
54     TableInsert = re.compile(r'[^#]*TableInsert\s+"([^"]*)"', re.IGNORECASE)
55     for src in input_files:
56         input = io.open(src, encoding='utf_8')
57         for lineno, line in enumerate(input.readlines()):
58             if Submenu.match(line):
59                 (string,) = Submenu.match(line).groups()
60                 string = string.replace('_', ' ')
61             elif Popupmenu.match(line):
62                 (string,) = Popupmenu.match(line).groups()
63             elif Dynamicmenu.match(line):
64                 (string,) = Dynamicmenu.match(line).groups()
65             elif IconPalette.match(line):
66                 (string,) = IconPalette.match(line).groups()
67             elif Toolbar.match(line):
68                 (string,) = Toolbar.match(line).groups()
69             elif Item.match(line):
70                 (string,) = Item.match(line).groups()
71             elif TableInsert.match(line):
72                 (string,) = TableInsert.match(line).groups()
73             else:
74                 continue
75             string = string.replace('"', '')
76             if string != "":
77                 print(u'#: %s:%d\nmsgid "%s"\nmsgstr ""\n' % \
78                     (relativePath(src, base), lineno+1, string), file=output)
79         input.close()
80     output.close()
81
82
83 def layouts_l10n(input_files, output, base, layouttranslations):
84     '''Generate pot file from lib/layouts/*.{layout,inc,module} and lib/citeengines/*.citeengine'''
85     ClassDescription = re.compile(r'^\s*#\s*\\Declare(LaTeX|DocBook)Class.*\{(.*)\}$', re.IGNORECASE)
86     ClassCategory = re.compile(r'^\s*#\s*\\DeclareCategory\{(.*)\}$', re.IGNORECASE)
87     Style = re.compile(r'^\s*Style\s+(.*\S)\s*$', re.IGNORECASE)
88     # match LabelString, EndLabelString, LabelStringAppendix and maybe others but no comments
89     LabelString = re.compile(r'^[^#]*LabelString\S*\s+(.*\S)\s*$', re.IGNORECASE)
90     MenuString = re.compile(r'^[^#]*MenuString\S*\s+(.*\S)\s*$', re.IGNORECASE)
91     OutlinerName = re.compile(r'^[^#]*OutlinerName\s+(\S+|\"[^\"]*\")\s+\"([^\"]*)\"', re.IGNORECASE)
92     Tooltip = re.compile(r'^\s*Tooltip\S*\s+(.*\S)\s*$', re.IGNORECASE)
93     GuiName = re.compile(r'^\s*GuiName\s+(.*\S)\s*$', re.IGNORECASE)
94     ListName = re.compile(r'^\s*ListName\s+(.*\S)\s*$', re.IGNORECASE)
95     CategoryName = re.compile(r'^\s*Category\s+(.*\S)\s*$', re.IGNORECASE)
96     NameRE = re.compile(r'^\s*#\s*\\DeclareLyXModule.*{(.*)}$', re.IGNORECASE)
97     CiteNameRE = re.compile(r'^\s*#\s*\\DeclareLyXCiteEngine.*\{(.*)\}$', re.IGNORECASE)
98     InsetLayout = re.compile(r'^InsetLayout\s+\"?(.*)\"?\s*$', re.IGNORECASE)
99     FlexCheck = re.compile(r'^Flex:(.*)', re.IGNORECASE)
100     CaptionCheck = re.compile(r'^Caption:(.*)', re.IGNORECASE)
101     DescBegin = re.compile(r'^\s*#\s*DescriptionBegin\s*$', re.IGNORECASE)
102     DescEnd = re.compile(r'^\s*#\s*DescriptionEnd\s*$', re.IGNORECASE)
103     Category = re.compile(r'^\s*#\s*Category:\s+(.*\S)\s*$', re.IGNORECASE)
104     I18nPreamble = re.compile(r'^\s*((Lang)|(Babel))Preamble\s*$', re.IGNORECASE)
105     EndI18nPreamble = re.compile(r'^\s*End((Lang)|(Babel))Preamble\s*$', re.IGNORECASE)
106     I18nString = re.compile(r'_\(([^\)]+)\)')
107     CounterFormat = re.compile(r'^\s*PrettyFormat\s+"?(.*)"?\s*$', re.IGNORECASE)
108     CiteFormat = re.compile(r'^\s*CiteFormat', re.IGNORECASE)
109     # Note: preceding and trailing space in the val below matters
110     KeyVal = re.compile(r'^\s*B?_\w+\s(.*\S)*$')
111     Float = re.compile(r'^\s*Float\s*$', re.IGNORECASE)
112     UsesFloatPkg = re.compile(r'^\s*UsesFloatPkg\s+(.*\S)\s*$', re.IGNORECASE)
113     IsPredefined = re.compile(r'^\s*IsPredefined\s+(.*\S)\s*$', re.IGNORECASE)
114     End = re.compile(r'^\s*End', re.IGNORECASE)
115     Comment = re.compile(r'^(.*)#')
116     Translation = re.compile(r'^\s*Translation\s+(.*\S)\s*$', re.IGNORECASE)
117     KeyValPair = re.compile(r'\s*"(.*)"\s+"(.*)"')
118
119     oldlanguages = []
120     languages = []
121     keyset = set()
122     oldtrans = dict()
123     if layouttranslations:
124         linguas_file = os.path.join(base, 'po/LINGUAS')
125         for line in open(linguas_file).readlines():
126             res = Comment.search(line)
127             if res:
128                 line = res.group(1)
129             if line.strip() != '':
130                 languages.extend(line.split())
131
132         # read old translations if available
133         try:
134             input = io.open(output, encoding='utf_8')
135             lang = ''
136             for line in input.readlines():
137                 res = Comment.search(line)
138                 if res:
139                     line = res.group(1)
140                 if line.strip() == '':
141                     continue
142                 res = Translation.search(line)
143                 if res:
144                     lang = res.group(1)
145                     if lang not in languages:
146                         oldlanguages.append(lang)
147                         languages.append(lang)
148                     oldtrans[lang] = dict()
149                     continue
150                 res = End.search(line)
151                 if res:
152                     lang = ''
153                     continue
154                 res = KeyValPair.search(line)
155                 if res and lang != '':
156                     key = res.group(1)
157                     val = res.group(2)
158                     key = key.replace('\\"', '"').replace('\\\\', '\\')
159                     val = val.replace('\\"', '"').replace('\\\\', '\\')
160                     oldtrans[lang][key] = val
161                     keyset.add(key)
162                     continue
163                 print("Error: Unable to handle line:")
164                 print(line)
165         except IOError:
166             print("Warning: Unable to open %s for reading." % output)
167             print("         Old translations will be lost.")
168
169         # walon is not a known document language
170         # FIXME: Do not hardcode, read from lib/languages!
171         if 'wa' in languages:
172             languages.remove('wa')
173
174     if layouttranslations:
175         out = io.open(output, 'w', encoding='utf_8')
176     else:
177         out = io.open(output, 'w', encoding='utf_8', newline='\n')
178     for src in input_files:
179         readingDescription = False
180         readingI18nPreamble = False
181         readingFloat = False
182         readingCiteFormats = False
183         isPredefined = False
184         usesFloatPkg = True
185         listname = ''
186         floatname = ''
187         descStartLine = -1
188         descLines = []
189         lineno = 0
190         for line in io.open(src, encoding='utf_8').readlines():
191             lineno += 1
192             res = ClassDescription.search(line)
193             if res != None:
194                 string = res.group(2)
195                 if not layouttranslations:
196                     writeString(out, src, base, lineno + 1, string)
197                 continue
198             res = ClassCategory.search(line)
199             if res != None:
200                 string = res.group(1)
201                 if not layouttranslations:
202                     writeString(out, src, base, lineno + 1, string)
203                 continue
204             if readingDescription:
205                 res = DescEnd.search(line)
206                 if res != None:
207                     readingDescription = False
208                     desc = " ".join(descLines)
209                     if not layouttranslations:
210                         writeString(out, src, base, lineno + 1, desc)
211                     continue
212                 descLines.append(line[1:].strip())
213                 continue
214             res = DescBegin.search(line)
215             if res != None:
216                 readingDescription = True
217                 descStartLine = lineno
218                 continue
219             if readingI18nPreamble:
220                 res = EndI18nPreamble.search(line)
221                 if res != None:
222                     readingI18nPreamble = False
223                     continue
224                 res = I18nString.search(line)
225                 if res != None:
226                     string = res.group(1)
227                     if layouttranslations:
228                         keyset.add(string)
229                     else:
230                         writeString(out, src, base, lineno, string)
231                 continue
232             res = I18nPreamble.search(line)
233             if res != None:
234                 readingI18nPreamble = True
235                 continue
236             res = NameRE.search(line)
237             if res != None:
238                 string = res.group(1)
239                 if not layouttranslations:
240                     writeString(out, src, base, lineno + 1, string)
241                 continue
242             res = CiteNameRE.search(line)
243             if res != None:
244                 string = res.group(1)
245                 if not layouttranslations:
246                     writeString(out, src, base, lineno + 1, string)
247                 continue
248             res = Style.search(line)
249             if res != None:
250                 string = res.group(1)
251                 string = string.replace('_', ' ')
252                 # Style means something else inside a float definition
253                 if not readingFloat:
254                     if not layouttranslations:
255                         writeString(out, src, base, lineno, string)
256                 continue
257             res = LabelString.search(line)
258             if res != None:
259                 string = res.group(1)
260                 if not layouttranslations:
261                     writeString(out, src, base, lineno, string)
262                 continue
263             res = MenuString.search(line)
264             if res != None:
265                 string = res.group(1)
266                 if not layouttranslations:
267                     writeString(out, src, base, lineno, string)
268                 continue
269             res = OutlinerName.search(line)
270             if res != None:
271                 string = res.group(2)
272                 if not layouttranslations:
273                     writeString(out, src, base, lineno, string)
274                 continue
275             res = Tooltip.search(line)
276             if res != None:
277                 string = res.group(1)
278                 if not layouttranslations:
279                     writeString(out, src, base, lineno, string)
280                 continue
281             res = GuiName.search(line)
282             if res != None:
283                 string = res.group(1)
284                 if layouttranslations:
285                     # gui name must only be added for floats
286                     if readingFloat:
287                         floatname = string
288                 else:
289                     writeString(out, src, base, lineno, string)
290                 continue
291             res = CategoryName.search(line)
292             if res != None:
293                 string = res.group(1)
294                 if not layouttranslations:
295                     writeString(out, src, base, lineno, string)
296                 continue
297             res = ListName.search(line)
298             if res != None:
299                 string = res.group(1)
300                 if layouttranslations:
301                     listname = string.strip('"')
302                 else:
303                     writeString(out, src, base, lineno, string)
304                 continue
305             res = InsetLayout.search(line)
306             if res != None:
307                 string = res.group(1)
308                 string = string.replace('_', ' ')
309                 #Flex:xxx is not used in translation
310                 #if not layouttranslations:
311                 #    writeString(out, src, base, lineno, string)
312                 m = FlexCheck.search(string)
313                 if m:
314                     if not layouttranslations:
315                         writeString(out, src, base, lineno, m.group(1))
316                 m = CaptionCheck.search(string)
317                 if m:
318                     if not layouttranslations:
319                         writeString(out, src, base, lineno, m.group(1))
320                 continue
321             res = Category.search(line)
322             if res != None:
323                 string = res.group(1)
324                 if not layouttranslations:
325                     writeString(out, src, base, lineno, string)
326                 continue
327             res = CounterFormat.search(line)
328             if res != None:
329                 string = res.group(1)
330                 if not layouttranslations:
331                     writeString(out, src, base, lineno, string)
332                 continue
333             res = Float.search(line)
334             if res != None:
335                 readingFloat = True
336                 continue
337             res = IsPredefined.search(line)
338             if res != None:
339                 string = res.group(1).lower()
340                 if string == 'true':
341                     isPredefined = True
342                 else:
343                     isPredefined = False
344                 continue
345             res = UsesFloatPkg.search(line)
346             if res != None:
347                 string = res.group(1).lower()
348                 if string == 'true':
349                     usesFloatPkg = True
350                 else:
351                     usesFloatPkg = False
352                 continue
353             res = CiteFormat.search(line)
354             if res != None:
355                 readingCiteFormats = True
356                 continue
357             res = End.search(line)
358             if res != None:
359                 # We have four combinations of the flags usesFloatPkg and isPredefined:
360                 #     usesFloatPkg and     isPredefined: might use standard babel translations
361                 #     usesFloatPkg and not isPredefined: does not use standard babel translations
362                 # not usesFloatPkg and     isPredefined: uses standard babel translations
363                 # not usesFloatPkg and not isPredefined: not supported by LyX
364                 # The third combination is even true for MarginFigure, MarginTable (both from
365                 # tufte-book.layout) and Planotable, Plate (both from aguplus.inc).
366                 if layouttranslations and readingFloat and usesFloatPkg:
367                     if floatname != '':
368                         keyset.add(floatname)
369                     if listname != '':
370                         keyset.add(listname)
371                 isPredefined = False
372                 usesFloatPkg = True
373                 listname = ''
374                 floatname = ''
375                 readingCiteFormats = False
376                 readingFloat = False
377                 continue
378             if readingCiteFormats:
379                 res = KeyVal.search(line)
380                 if res != None:
381                     val = res.group(1)
382                     if not layouttranslations:
383                         writeString(out, src, base, lineno, val)
384
385     if layouttranslations:
386         # Extract translations of layout files
387         import polib
388
389         # Sort languages and key to minimize the diff between different runs
390         # with changed translations
391         languages.sort()
392         keys = []
393         for key in keyset:
394             keys.append(key)
395         keys.sort()
396
397         ContextRe = re.compile(r'(.*)(\[\[.*\]\])')
398
399         print(u'''# This file has been automatically generated by po/lyx_pot.py.
400 # PLEASE MODIFY ONLY THE LAGUAGES HAVING NO .po FILE! If you want to regenerate
401 # this file from the translations, run `make ../lib/layouttranslations' in po.
402 # Python polib library is needed for building the output file.
403 #
404 # This file should remain fixed during minor LyX releases.
405 # For more comments see README.localization file.''', file=out)
406         for lang in languages:
407             print(u'\nTranslation %s' % lang, file=out)
408             if lang in list(oldtrans.keys()):
409                 trans = oldtrans[lang]
410             else:
411                 trans = dict()
412             if not lang in oldlanguages:
413                 poname = os.path.join(base, 'po/' + lang + '.po')
414                 po = polib.pofile(poname)
415                 # Iterate through po entries and not keys for speed reasons.
416                 # FIXME: The code is still too slow
417                 for entry in po:
418                     if not entry.translated():
419                         continue
420                     if entry.msgid in keys:
421                         key = entry.msgid
422                         val = entry.msgstr
423                         # some translators keep untranslated entries
424                         if val != key:
425                             trans[key] = val
426             for key in keys:
427                 if key in list(trans.keys()):
428                     val = trans[key].replace('\\', '\\\\').replace('"', '\\"')
429                     res = ContextRe.search(val)
430                     if res != None:
431                         val = res.group(1)
432                     key = key.replace('\\', '\\\\').replace('"', '\\"')
433                     print(u'\t"%s" "%s"' % (key, val), file=out)
434                 # also print untranslated entries to help translators
435                 elif not lang in oldlanguages:
436                     key = key.replace('\\', '\\\\').replace('"', '\\"')
437                     res = ContextRe.search(key)
438                     if res != None:
439                         val = res.group(1)
440                     else:
441                         val = key
442                     print(u'\t"%s" "%s"' % (key, val), file=out)
443             print(u'End', file=out)
444
445     out.close()
446
447
448 def qt_l10n(input_files, output, base):
449     '''Generate pot file from src/frontends/qt/ui/*.ui'''
450     output = io.open(output, 'w', encoding='utf_8', newline='\n')
451     pat = re.compile(r'\s*<string>(.*)</string>')
452     prop = re.compile(r'\s*<property.*name.*=.*shortcut')
453     for src in input_files:
454         input = io.open(src, encoding='utf_8')
455         skipNextLine = False
456         for lineno, line in enumerate(input.readlines()):
457             # skip the line after <property name=shortcut>
458             if skipNextLine:
459                 skipNextLine = False
460                 continue
461             if prop.match(line):
462                 skipNextLine = True
463                 continue
464             # get lines that match <string>...</string>
465             if pat.match(line):
466                 (string,) = pat.match(line).groups()
467                 string = string.replace('&amp;', '&').replace('&quot;', '"')
468                 string = string.replace('&lt;', '<').replace('&gt;', '>')
469                 string = string.replace('\\', '\\\\').replace('"', r'\"')
470                 string = string.replace('&#x0a;', r'\n')
471                 print(u'#: %s:%d\nmsgid "%s"\nmsgstr ""\n' % \
472                     (relativePath(src, base), lineno+1, string), file=output)
473         input.close()
474     output.close()
475
476
477 def languages_l10n(input_files, output, base):
478     '''Generate pot file from lib/languages'''
479     out = io.open(output, 'w', encoding='utf_8', newline='\n')
480     GuiName = re.compile(r'^[^#]*GuiName\s+(.*)', re.IGNORECASE)
481
482     for src in input_files:
483         descStartLine = -1
484         descLines = []
485         lineno = 0
486         for line in io.open(src, encoding='utf_8').readlines():
487             lineno += 1
488             res = GuiName.search(line)
489             if res != None:
490                 string = res.group(1)
491                 writeString(out, src, base, lineno, string)
492                 continue
493
494     out.close()
495
496
497 def latexfonts_l10n(input_files, output, base):
498     '''Generate pot file from lib/latexfonts'''
499     out = io.open(output, 'w', encoding='utf_8', newline='\n')
500     GuiName = re.compile(r'^[^#]*GuiName\s+(.*)', re.IGNORECASE)
501
502     for src in input_files:
503         descStartLine = -1
504         descLines = []
505         lineno = 0
506         for line in io.open(src, encoding='utf_8').readlines():
507             lineno += 1
508             res = GuiName.search(line)
509             if res != None:
510                 string = res.group(1)
511                 writeString(out, src, base, lineno, string)
512                 continue
513
514     out.close()
515
516
517 def external_l10n(input_files, output, base):
518     '''Generate pot file from lib/xtemplates'''
519     output = io.open(output, 'w', encoding='utf_8', newline='\n')
520     Template = re.compile(r'^Template\s+(.*)', re.IGNORECASE)
521     GuiName = re.compile(r'\s*GuiName\s+(.*)', re.IGNORECASE)
522     HelpTextStart = re.compile(r'\s*HelpText\s', re.IGNORECASE)
523     HelpTextSection = re.compile(r'\s*(\S.*)\s*$')
524     HelpTextEnd = re.compile(r'\s*HelpTextEnd\s', re.IGNORECASE)
525     i = -1
526     for src in input_files:
527         input = io.open(src, encoding='utf_8')
528         inHelp = False
529         hadHelp = False
530         prev_help_string = ''
531         for lineno, line in enumerate(input.readlines()):
532             if Template.match(line):
533                 (string,) = Template.match(line).groups()
534             elif GuiName.match(line):
535                 (string,) = GuiName.match(line).groups()
536             elif inHelp:
537                 if HelpTextEnd.match(line):
538                     if hadHelp:
539                         print(u'\nmsgstr ""\n', file=output)
540                     inHelp = False
541                     hadHelp = False
542                     prev_help_string = ''
543                 elif HelpTextSection.match(line):
544                     (help_string,) = HelpTextSection.match(line).groups()
545                     help_string = help_string.replace('"', '')
546                     help_string =  help_string.replace('\\', '_backsl_')
547                     help_string =  help_string.replace('_backsl_', '\\\\')
548                     if help_string != "" and prev_help_string == '':
549                         print(u'#: %s:%d\nmsgid ""\n"%s\\n"' % \
550                             (relativePath(src, base), lineno+1, help_string), file=output)
551                         hadHelp = True
552                     elif help_string != "":
553                         print(u'"%s\\n"' % help_string, file=output)
554                     prev_help_string = help_string
555                 else:
556                     # Empty line
557                     print(u'"\\n"', file=output)
558                     prev_help_string = 'xxxx'
559             elif HelpTextStart.match(line):
560                 inHelp = True
561                 prev_help_string = ''
562             else:
563                 continue
564             string = string.replace('"', '')
565             if string != "" and not inHelp:
566                 print(u'#: %s:%d\nmsgid "%s"\nmsgstr ""\n' % \
567                     (relativePath(src, base), lineno+1, string), file=output)
568         input.close()
569     output.close()
570
571
572 def formats_l10n(input_files, output, base):
573     '''Generate pot file from configure.py'''
574     output = io.open(output, 'w', encoding='utf_8', newline='\n')
575     GuiName = re.compile(r'.*\\Format\s+\S+\s+\S+\s+"([^"]*)"\s+(\S*)\s+.*', re.IGNORECASE)
576     GuiName2 = re.compile(r'.*\\Format\s+\S+\s+\S+\s+([^"]\S+)\s+(\S*)\s+.*', re.IGNORECASE)
577     input = io.open(input_files[0], encoding='utf_8')
578     for lineno, line in enumerate(input.readlines()):
579         label = ""
580         labelsc = ""
581         if GuiName.match(line):
582             label = GuiName.match(line).group(1)
583             shortcut = GuiName.match(line).group(2).replace('"', '')
584         elif GuiName2.match(line):
585             label = GuiName2.match(line).group(1)
586             shortcut = GuiName2.match(line).group(2).replace('"', '')
587         else:
588             continue
589         label = label.replace('\\', '\\\\').replace('"', '')
590         if shortcut != "":
591             labelsc = label + "|" + shortcut
592         if label != "":
593             print(u'#: %s:%d\nmsgid "%s"\nmsgstr ""\n' % \
594                 (relativePath(input_files[0], base), lineno+1, label), file=output)
595         if labelsc != "":
596             print(u'#: %s:%d\nmsgid "%s"\nmsgstr ""\n' % \
597                 (relativePath(input_files[0], base), lineno+1, labelsc), file=output)
598     input.close()
599     output.close()
600
601
602 def encodings_l10n(input_files, output, base):
603     '''Generate pot file from lib/encodings'''
604     output = io.open(output, 'w', encoding='utf_8', newline='\n')
605     # assuming only one encodings file
606     #                 Encoding utf8      utf8    "Unicode (utf8)" UTF-8    variable inputenc
607     reg = re.compile('Encoding [\w-]+\s+[\w-]+\s+"([\w \-\(\)\[\]\/^"]*)"\s+["\w-]+\s+(fixed|variable|variableunsafe)\s+\w+.*')
608     input = io.open(input_files[0], encoding='utf_8')
609     for lineno, line in enumerate(input.readlines()):
610         if not line.startswith('Encoding'):
611             continue
612         if reg.match(line):
613             guiname = reg.match(line).groups()[0]
614             if guiname != "":
615                 print(u'#: %s:%d\nmsgid "%s"\nmsgstr ""\n' % \
616                     (relativePath(input_files[0], base), lineno+1, guiname), file=output)
617         else:
618             print("Error: Unable to handle line:")
619             print(line)
620             # No need to abort if the parsing fails
621             # sys.exit(1)
622     input.close()
623     output.close()
624
625
626 def examples_templates_l10n(input_files, output, base):
627   '''Generate pot file from lib/templates and lib/examples'''
628   output = io.open(output, 'w', encoding='utf_8', newline='\n')
629   # only record each item once
630   seen = []
631   for src in input_files:
632       parseExamplesTemplates(src, seen, output)
633   output.close()
634
635
636 def parseExamplesTemplates(file, seen, output):
637   # Recursively iterate over subdirectories
638   if os.path.isdir(file):
639       for sfile in glob.glob( os.path.join(file, '*') ):
640           parseExamplesTemplates(sfile, seen, output)
641
642   filename = os.path.normpath(os.path.realpath(file)).split(os.sep)[-1]
643   if os.path.isfile(file):
644       if filename[-4:] != ".lyx":
645           return
646       filename = filename[:-4]
647   if seen.count(filename) or filename[0].islower():
648       return
649
650   seen.append(filename)
651   if filename != "":
652       print(u'#: %s:%d\nmsgid "%s"\nmsgstr ""\n' % \
653                 (relativePath(input_files[0], base), 0, filename.replace('_', ' ').replace('%26', '&').replace('%28', '(').replace('%29', ')')), file=output)
654
655
656 def tabletemplates_l10n(input_files, output, base):
657   '''Generate pot file from lib/tabletemplates '''
658   output = io.open(output, 'w', encoding='utf_8', newline='\n')
659   # only record each item once
660   seen = []
661   for file in input_files:
662       filename = os.path.normpath(os.path.realpath(file)).split(os.sep)[-1]
663       if os.path.isfile(file):
664           if filename[-4:] != ".lyx":
665               continue
666           filename = filename[:-4]
667           if filename[-4:-1] == "_1x":
668               continue
669           if seen.count(filename):
670               continue
671
672       seen.append(filename)
673       if filename != "":
674           print(u'#: %s:%d\nmsgid "%s"\nmsgstr ""\n' % \
675                     (relativePath(input_files[0], base), 0, filename.replace('_', ' ')), file=output)
676   output.close()
677   
678
679
680 Usage = '''
681 lyx_pot.py [-b|--base top_src_dir] [-o|--output output_file] [-h|--help] [-s|src_file filename] -t|--type input_type input_files
682
683 where
684     --base:
685         path to the top source directory. default to '.'
686     --output:
687         output pot file, default to './lyx.pot'
688     --src_file
689         filename that contains a list of input files in each line
690     --input_type can be
691         ui: lib/ui/*
692         layouts: lib/layouts/*
693         layouttranslations: create lib/layouttranslations from po/*.po and lib/layouts/*
694         qt: qt ui files
695         languages: file lib/languages
696         latexfonts: file lib/latexfonts
697         encodings: file lib/encodings
698         external: external templates files
699         formats: formats predefined in lib/configure.py
700         examples_templates: example and template files
701         tabletemplates: table template files
702 '''
703
704 if __name__ == '__main__':
705     input_type = None
706     output = 'lyx.pot'
707     base = '.'
708     input_files = []
709     #
710     optlist, args = getopt.getopt(sys.argv[1:], 'ht:o:b:s:',
711         ['help', 'type=', 'output=', 'base=', 'src_file='])
712     for (opt, value) in optlist:
713         if opt in ['-h', '--help']:
714             print(Usage)
715             sys.exit(0)
716         elif opt in ['-o', '--output']:
717             output = value
718         elif opt in ['-b', '--base']:
719             base = value
720         elif opt in ['-t', '--type']:
721             input_type = value
722         elif opt in ['-s', '--src_file']:
723             input_files = [f.strip() for f in io.open(value, encoding='utf_8')]
724
725     if input_type not in ['ui', 'layouts', 'layouttranslations', 'qt', 'languages', 'latexfonts', 'encodings', 'external', 'formats', 'examples_templates', 'tabletemplates'] or output is None:
726         print('Wrong input type or output filename.')
727         sys.exit(1)
728
729     input_files += args
730
731     # Ensure a unique sorting of input files and ignore the order in which they
732     # are given on the command line. This is important to avoid huge
733     # pseudo-diffs in the generated .pot file which would then end up in the
734     # .po files as well. We had this situation for years with people using
735     # different build systems to remerge .po files.
736     input_files.sort()
737
738     if input_type == 'ui':
739         ui_l10n(input_files, output, base)
740     elif input_type == 'latexfonts':
741         latexfonts_l10n(input_files, output, base)
742     elif input_type == 'layouts':
743         layouts_l10n(input_files, output, base, False)
744     elif input_type == 'layouttranslations':
745         layouts_l10n(input_files, output, base, True)
746     elif input_type == 'qt':
747         qt_l10n(input_files, output, base)
748     elif input_type == 'external':
749         external_l10n(input_files, output, base)
750     elif input_type == 'formats':
751         formats_l10n(input_files, output, base)
752     elif input_type == 'encodings':
753         encodings_l10n(input_files, output, base)
754     elif input_type == 'examples_templates':
755         examples_templates_l10n(input_files, output, base)
756     elif input_type == 'tabletemplates':
757         tabletemplates_l10n(input_files, output, base)
758     else:
759         languages_l10n(input_files, output, base)