modinfo in the translate toolkit is now a 2-tuple containing the mtime and
[pootle.git] / potree.py
blob4b4ff95b9725cdbc409d2a8fe96bb9bedb82ff57
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # Copyright 2004-2006 Zuza Software Foundation
5 #
6 # This file is part of translate.
8 # translate is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # translate is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with translate; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 """manages the whole set of projects and languages for a pootle installation"""
24 from Pootle import projects
25 from Pootle import pootlefile
26 from Pootle import pagelayout
27 from translate.misc import autoencode
28 import os
29 import re
31 languagere = re.compile("^[a-z]{2,3}([_-][A-Z]{2,3}|)$")
32 regionre = re.compile("^[_-][A-Z]{2,3}$")
34 class POTree:
35 """Manages the tree of projects and languages"""
36 def __init__(self, instance):
37 self.languages = instance.languages
38 if not self.haslanguage("templates"):
39 setattr(self.languages, "templates.fullname", "Templates")
40 self.saveprefs()
41 self.projects = instance.projects
42 self.podirectory = instance.podirectory
43 self.instance = instance
44 self.projectcache = {}
46 def saveprefs(self):
47 """saves any changes made to the preferences"""
48 # TODO: this is a hack, fix it up nicely :-)
49 prefsfile = self.languages.__root__.__dict__["_setvalue"].im_self
50 prefsfile.savefile()
52 def changelanguages(self, argdict):
53 """changes language entries"""
54 for key, value in argdict.iteritems():
55 if key.startswith("languageremove-"):
56 languagecode = key.replace("languageremove-", "", 1)
57 if self.haslanguage(languagecode):
58 delattr(self.languages, languagecode)
59 elif key.startswith("languagename-"):
60 languagecode = key.replace("languagename-", "", 1)
61 if self.haslanguage(languagecode):
62 languagename = self.getlanguagename(languagecode)
63 if languagename != value:
64 self.setlanguagename(languagecode, value)
65 elif key.startswith("languagespecialchars-"):
66 languagecode = key.replace("languagespecialchars-", "", 1)
67 if self.haslanguage(languagecode):
68 languagespecialchars = self.getlanguagespecialchars(languagecode)
69 if languagespecialchars != value:
70 self.setlanguagespecialchars(languagecode, value)
71 elif key.startswith("languagenplurals-"):
72 languagecode = key.replace("languagenplurals-", "", 1)
73 if self.haslanguage(languagecode):
74 languagenplurals = self.getlanguagenplurals(languagecode)
75 if languagenplurals != value:
76 self.setlanguagenplurals(languagecode, value)
77 elif key.startswith("languagepluralequation-"):
78 languagecode = key.replace("languagepluralequation-", "", 1)
79 if self.haslanguage(languagecode):
80 languagepluralequation = self.getlanguagepluralequation(languagecode)
81 if languagepluralequation != value:
82 self.setlanguagepluralequation(languagecode, value)
83 elif key == "newlanguagecode":
84 languagecode = value.lower()
85 if not languagecode.strip():
86 continue
87 if not languagecode.isalpha():
88 languagecode = pagelayout.localelanguage(languagecode)
89 if languagecode.find("_") >= 0:
90 for part in languagecode.split("_"):
91 if not part.isalpha():
92 raise ValueError("Language code must be alphabetic")
93 languagecode, countrycode = languagecode.split("_")
94 countrycode = countrycode.upper()
95 languagecode = "%s_%s" % (languagecode, countrycode)
96 else:
97 raise ValueError("Language code must be alphabetic")
98 if self.haslanguage(languagecode):
99 raise ValueError("Already have language with the code %s" % languagecode)
100 languagename = argdict.get("newlanguagename", languagecode)
101 languagespecialchars = argdict.get("newlanguagespecialchars", "")
102 languagenplurals = argdict.get("newlanguagenplurals", "")
103 languagepluralequation = argdict.get("newlanguagepluralequation", "")
104 # FIXME need to check that default values are not present
105 # if languagename == self.localize("(add language here)"):
106 # raise ValueError("Please set a value for the language name")
107 print "nplurals: %s" % languagenplurals
108 if not languagenplurals.isdigit() and not languagenplurals == "":
109 raise ValueError("Number of plural forms must be numeric")
110 # if languagenplurals == self.localize("(number of plurals)"):
111 # raise ValueError("Please set a value for the number of plural forms")
112 # if languagepluralequation == self.localize("(plural equation)"):
113 # raise ValueError("Please set a value for the plural equation")
114 if not languagenplurals == "" and languagepluralequation == "":
115 raise ValueError("Please set both the number of plurals and the plural equation OR leave both blank")
116 setattr(self.languages, languagecode + ".fullname", languagename)
117 setattr(self.languages, languagecode + ".specialchars", languagespecialchars)
118 setattr(self.languages, languagecode + ".nplurals", languagenplurals)
119 setattr(self.languages, languagecode + ".pluralequation", languagepluralequation)
120 self.saveprefs()
122 def changeprojects(self, argdict):
123 """changes project entries"""
124 #Let's reset all "createmofiles" to 0, otherwise we can't disable one
125 #since the key will never arrive
126 for project in self.getprojectcodes():
127 self.setprojectcreatemofiles(project, 0)
128 for key, value in argdict.iteritems():
129 if key.startswith("projectremove-"):
130 projectcode = key.replace("projectremove-", "", 1)
131 if hasattr(self.projects, projectcode):
132 delattr(self.projects, projectcode)
133 elif key.startswith("projectname-"):
134 projectcode = key.replace("projectname-", "", 1)
135 if hasattr(self.projects, projectcode):
136 projectname = self.getprojectname(projectcode)
137 if projectname != value:
138 self.setprojectname(projectcode, value)
139 elif key.startswith("projectdescription-"):
140 projectcode = key.replace("projectdescription-", "", 1)
141 if hasattr(self.projects, projectcode):
142 projectdescription = self.getprojectdescription(projectcode)
143 if projectdescription != value:
144 self.setprojectdescription(projectcode, value)
145 elif key.startswith("projectcheckerstyle-"):
146 projectcode = key.replace("projectcheckerstyle-", "", 1)
147 if hasattr(self.projects, projectcode):
148 projectcheckerstyle = self.getprojectcheckerstyle(projectcode)
149 if projectcheckerstyle != value:
150 self.setprojectcheckerstyle(projectcode, value)
151 elif key.startswith("projectfiletype-"):
152 projectcode = key.replace("projectfiletype-", "", 1)
153 if hasattr(self.projects, projectcode):
154 projectlocalfiletype = self.getprojectlocalfiletype(projectcode)
155 if projectlocalfiletype != value:
156 self.setprojectlocalfiletype(projectcode, value)
157 elif key.startswith("projectcreatemofiles-"):
158 projectcode = key.replace("projectcreatemofiles-", "", 1)
159 if hasattr(self.projects, projectcode):
160 self.setprojectcreatemofiles(projectcode, 1)
161 elif key == "newprojectcode":
162 projectcode = value.lower()
163 if not projectcode:
164 continue
165 if not (projectcode[:1].isalpha() and projectcode.replace("_","").isalnum()):
166 raise ValueError("Project code must be alphanumeric and start with an alphabetic character (got %r)" % projectcode)
167 if hasattr(self.projects, projectcode):
168 raise ValueError("Already have project with the code %s" % projectcode)
169 projectname = argdict.get("newprojectname", projectcode)
170 projecttype = argdict.get("newprojectfiletype", "")
171 projectdescription = argdict.get("newprojectdescription", "")
172 projectcheckerstyle = argdict.get("newprojectcheckerstyle", "")
173 projectcreatemofiles = argdict.get("newprojectcreatemofiles", "")
174 setattr(self.projects, projectcode + ".fullname", projectname)
175 setattr(self.projects, projectcode + ".localfiletype", projecttype)
176 setattr(self.projects, projectcode + ".description", projectdescription)
177 setattr(self.projects, projectcode + ".checkerstyle", projectcheckerstyle)
178 setattr(self.projects, projectcode + ".createmofiles", projectcreatemofiles)
179 projectdir = os.path.join(self.podirectory, projectcode)
180 if not os.path.isdir(projectdir):
181 os.mkdir(projectdir)
182 self.saveprefs()
184 def haslanguage(self, languagecode):
185 """checks if this language exists"""
186 return hasattr(self.languages, languagecode)
188 def getlanguageprefs(self, languagecode):
189 """returns the language object"""
190 return getattr(self.languages, languagecode)
192 def getlanguagename(self, languagecode):
193 """returns the language's full name"""
194 return getattr(self.getlanguageprefs(languagecode), "fullname", languagecode)
196 def setlanguagename(self, languagecode, languagename):
197 """stes the language's full name"""
198 setattr(self.getlanguageprefs(languagecode), "fullname", languagename)
200 def getlanguagespecialchars(self, languagecode):
201 """returns the language's special characters"""
202 return autoencode.autoencode(getattr(self.getlanguageprefs(languagecode), "specialchars", ""), "utf-8")
204 def setlanguagespecialchars(self, languagecode, languagespecialchars):
205 """sets the language's special characters"""
206 setattr(self.getlanguageprefs(languagecode), "specialchars", languagespecialchars)
208 def getlanguagenplurals(self, languagecode):
209 """returns the language's number of plural forms"""
210 return getattr(self.getlanguageprefs(languagecode), "nplurals", "")
212 def setlanguagenplurals(self, languagecode, languagenplurals):
213 """sets the language's number of plural forms"""
214 setattr(self.getlanguageprefs(languagecode), "nplurals", languagenplurals)
216 def getlanguagepluralequation(self, languagecode):
217 """returns the language's number of plural forms"""
218 return getattr(self.getlanguageprefs(languagecode), "pluralequation", "")
220 def setlanguagepluralequation(self, languagecode, languagepluralequation):
221 """sets the language's number of plural forms"""
222 setattr(self.getlanguageprefs(languagecode), "pluralequation", languagepluralequation)
224 def getlanguagecodes(self, projectcode=None):
225 """returns a list of valid languagecodes for a given project or all projects"""
226 alllanguagecodes = [languagecode for languagecode, language in self.languages.iteritems()]
227 if projectcode is None:
228 languagecodes = alllanguagecodes
229 else:
230 projectdir = os.path.join(self.podirectory, projectcode)
231 if not os.path.exists(projectdir):
232 return []
233 if self.isgnustyle(projectcode):
234 languagecodes = [languagecode for languagecode in alllanguagecodes if self.hasproject(languagecode, projectcode)]
235 else:
236 subdirs = [fn for fn in os.listdir(projectdir) if os.path.isdir(os.path.join(projectdir, fn))]
237 languagecodes = []
238 for potentialcode in subdirs:
239 if not self.languagematch(None, potentialcode):
240 continue
241 if potentialcode in alllanguagecodes:
242 languagecodes.append(potentialcode)
243 continue
244 if "-" in potentialcode:
245 potentialcode = potentialcode[:potentialcode.find("-")]
246 elif "_" in potentialcode:
247 potentialcode = potentialcode[:potentialcode.find("_")]
248 if potentialcode in alllanguagecodes:
249 languagecodes.append(potentialcode)
250 languagecodes.sort()
251 return languagecodes
253 def getlanguages(self, projectcode=None, sortbyname=True):
254 """gets a list of (languagecode, languagename) tuples"""
255 languagecodes = self.getlanguagecodes(projectcode)
256 if sortbyname:
257 languages = [(self.getlanguagename(languagecode), languagecode) for languagecode in languagecodes]
258 languages.sort()
259 return [(languagecode, languagename) for languagename, languagecode in languages]
260 else:
261 return [(languagecode, self.getlanguagename(languagecode)) for languagecode in languagecodes]
263 def getprojectcodes(self, languagecode=None):
264 """returns a list of project codes that are valid for the given languagecode or all projects"""
265 projectcodes = [projectcode for projectcode, projectprefs in self.projects.iteritems()]
266 projectcodes.sort()
267 if languagecode is None:
268 return projectcodes
269 else:
270 return [projectcode for projectcode in projectcodes if self.hasproject(languagecode, projectcode)]
272 def hasproject(self, languagecode, projectcode):
273 """returns whether the project exists for the language"""
274 if not hasattr(self.projects, projectcode):
275 return False
276 if languagecode is None:
277 return True
278 if not self.haslanguage(languagecode):
279 return False
280 try:
281 self.getpodir(languagecode, projectcode)
282 return True
283 except IndexError:
284 return False
286 def gettemplates(self, projectcode):
287 """returns templates for the given project"""
288 projectdir = os.path.join(self.podirectory, projectcode)
289 templatesdir = os.path.join(projectdir, "templates")
290 if not os.path.exists(templatesdir):
291 templatesdir = os.path.join(projectdir, "pot")
292 if not os.path.exists(templatesdir):
293 templatesdir = projectdir
294 potfilenames = []
295 def addfiles(podir, dirname, fnames):
296 """adds the files to the set of files for this project"""
297 basedirname = dirname.replace(podir, "", 1)
298 while basedirname.startswith(os.sep):
299 basedirname = basedirname.replace(os.sep, "", 1)
300 ponames = [fname for fname in fnames if fname.endswith(os.extsep+"pot")]
301 potfilenames.extend([os.path.join(basedirname, poname) for poname in ponames])
302 os.path.walk(templatesdir, addfiles, templatesdir)
303 return potfilenames
305 def getproject(self, languagecode, projectcode):
306 """returns the project object for the languagecode and projectcode"""
307 if (languagecode, projectcode) not in self.projectcache:
308 if languagecode == "templates":
309 self.projectcache[languagecode, projectcode] = projects.TemplatesProject(projectcode, self)
310 else:
311 self.projectcache[languagecode, projectcode] = projects.TranslationProject(languagecode, projectcode, self)
312 return self.projectcache[languagecode, projectcode]
314 def isgnustyle(self, projectcode):
315 """checks whether the whole project is a GNU-style project"""
316 projectdir = os.path.join(self.podirectory, projectcode)
317 return self.hasgnufiles(projectdir)
319 def addtranslationproject(self, languagecode, projectcode):
320 """creates a new TranslationProject"""
321 if self.hasproject(languagecode, projectcode):
322 raise ValueError("projects.TranslationProject for project %s, language %s already exists" % (projectcode, languagecode))
323 self.projectcache[languagecode, projectcode] = projects.TranslationProject(languagecode, projectcode, self, create=True)
325 def getprojectname(self, projectcode):
326 """returns the full name of the project"""
327 projectprefs = getattr(self.projects, projectcode)
328 return getattr(projectprefs, "fullname", projectcode)
330 def setprojectname(self, projectcode, projectname):
331 """returns the full name of the project"""
332 projectprefs = getattr(self.projects, projectcode)
333 setattr(projectprefs, "fullname", projectname)
335 def getprojectdescription(self, projectcode):
336 """returns the project description"""
337 projectprefs = getattr(self.projects, projectcode)
338 return getattr(projectprefs, "description", projectcode)
340 def setprojectdescription(self, projectcode, projectdescription):
341 """returns the project description"""
342 projectprefs = getattr(self.projects, projectcode)
343 setattr(projectprefs, "description", projectdescription)
345 def getprojectlocalfiletype(self, projectcode):
346 """returns the project allowed file type. We assume it is .po if nothing
347 else is specified."""
348 projectprefs = getattr(self.projects, projectcode)
349 type = getattr(projectprefs, "localfiletype", "po")
350 if not type:
351 type = "po"
352 return type
354 def setprojectlocalfiletype(self, projectcode, projectfiletype):
355 """sets the allowed file type for the project"""
356 projectprefs = getattr(self.projects, projectcode)
357 setattr(projectprefs, "localfiletype", projectfiletype)
359 def getprojectcheckerstyle(self, projectcode):
360 """returns the project checker style"""
361 projectprefs = getattr(self.projects, projectcode)
362 return getattr(projectprefs, "checkerstyle", projectcode)
364 def setprojectcheckerstyle(self, projectcode, projectcheckerstyle):
365 """sets the project checker style"""
366 projectprefs = getattr(self.projects, projectcode)
367 setattr(projectprefs, "checkerstyle", projectcheckerstyle)
369 def getprojectcreatemofiles(self, projectcode):
370 """returns whether the project builds MO files"""
371 projectprefs = getattr(self.projects, projectcode)
372 return getattr(projectprefs, "createmofiles", False)
374 def setprojectcreatemofiles(self, projectcode, projectcreatemofiles):
375 """sets whether the project builds MO files"""
376 projectprefs = getattr(self.projects, projectcode)
377 setattr(projectprefs, "createmofiles", projectcreatemofiles)
379 def hasgnufiles(self, podir, languagecode=None, depth=0, maxdepth=3, poext="po"):
380 """returns whether this directory contains gnu-style PO filenames for the given language"""
381 try:
382 if (podir.startswith(self.podirectory)):
383 def getprojectcode(podir=podir):
384 """Get the projectcode using the supplied podir."""
385 dirs = podir[len(self.podirectory):].split(os.sep)
386 if len(dirs[0]):
387 projectcode = dirs[0]
388 else:
389 projectcode = dirs[1]
390 return projectcode
391 projectprefs = getattr(self.projects, getprojectcode())
392 style = getattr(projectprefs, "treestyle")
393 if style == "gnu" \
394 or style == "nongnu":
395 return style
396 else:
397 print "Unsupported treestyle value (project %s): %s"%(projectcode,style)
398 except:
399 pass
400 #Let's check to see if we specifically find the correct gnu file
401 foundgnufile = False
402 if not os.path.isdir(podir):
403 return False
404 fnames = os.listdir(podir)
405 poext = os.extsep + "po"
406 subdirs = []
407 for fn in fnames:
408 if os.path.isdir(os.path.join(podir, fn)):
409 # if we have a language subdirectory, we're probably not GNU-style
410 if self.languagematch(languagecode, fn):
411 return False
412 #ignore hidden directories (like index directories)
413 if fn[0] == '.':
414 continue
415 subdirs.append(os.path.join(podir, fn))
416 elif fn.endswith(poext):
417 if self.languagematch(languagecode, fn[:-len(poext)]):
418 foundgnufile = True
419 elif not self.languagematch(None, fn[:-len(poext)]):
420 return "nongnu"
421 if depth < maxdepth:
422 for subdir in subdirs:
423 style = self.hasgnufiles(subdir, languagecode, depth+1, maxdepth)
424 if style == "nongnu":
425 return "nongnu"
426 if style == "gnu":
427 foundgnufile = True
429 if foundgnufile:
430 return "gnu"
431 else:
432 return ""
434 def getcodesfordir(self, dirname):
435 """returns projectcode and languagecode if dirname is a project directory"""
436 canonicalpath = lambda path: os.path.normcase(os.path.normpath(os.path.realpath(os.path.abspath(path))))
437 dirname = canonicalpath(dirname)
438 podirectory = canonicalpath(self.podirectory)
439 if dirname == podirectory:
440 return "*", None
441 for projectcode, projectprefs in self.projects.iteritems():
442 projectdir = canonicalpath(os.path.join(self.podirectory, projectcode))
443 if projectdir == dirname:
444 return projectcode, None
445 for languagecode, languageprefs in self.languages.iteritems():
446 languagedir = canonicalpath(os.path.join(projectdir, languagecode))
447 if not os.path.exists(languagedir):
448 languagedirs = [canonicalpath(languagedir) for languagedir in os.listdir(projectdir) if self.languagematch(languagecode, languagedir)]
449 if dirname in languagedirs:
450 return projectcode, languagecode
451 elif languagedir == dirname:
452 return projectcode, languagecode
453 return None, None
455 def getpodir(self, languagecode, projectcode):
456 """returns the base directory containing po files for the project"""
457 projectdir = os.path.join(self.podirectory, projectcode)
458 if not os.path.exists(projectdir):
459 raise IndexError("directory not found for project %s" % (projectcode))
460 languagedir = os.path.join(projectdir, languagecode)
461 if not os.path.exists(languagedir):
462 languagedirs = [languagedir for languagedir in os.listdir(projectdir) if self.languagematch(languagecode, languagedir)]
463 if not languagedirs:
464 # if no matching directories can be found, check if it is a GNU-style project
465 if self.hasgnufiles(projectdir, languagecode) == "gnu":
466 return projectdir
467 raise IndexError("directory not found for language %s, project %s" % (languagecode, projectcode))
468 # TODO: handle multiple regions
469 if len(languagedirs) > 1:
470 raise IndexError("multiple regions defined for language %s, project %s" % (languagecode, projectcode))
471 languagedir = os.path.join(projectdir, languagedirs[0])
472 return languagedir
474 def languagematch(self, languagecode, otherlanguagecode):
475 """matches a languagecode to another, ignoring regions in the second"""
476 if languagecode is None:
477 return languagere.match(otherlanguagecode)
478 return languagecode == otherlanguagecode or \
479 (otherlanguagecode.startswith(languagecode) and regionre.match(otherlanguagecode[len(languagecode):]))
481 def getpofiles(self, languagecode, projectcode, poext="po"):
482 """returns a list of po files for the project and language"""
483 pofilenames = []
484 prefix = os.curdir + os.sep
486 def addfiles(podir, dirname, fnames):
487 """adds the files to the set of files for this project"""
488 if dirname == os.curdir:
489 basedirname = ""
490 else:
491 basedirname = dirname.replace(prefix, "", 1)
492 for fname in fnames:
493 # check that it actually exists (to avoid problems with broken symbolic
494 # links, for example)
495 fpath = os.path.join(basedirname, fname)
496 if fname.endswith(os.extsep+poext):
497 pofilenames.append(fpath)
499 def addgnufiles(podir, dirname, fnames):
500 """adds the files to the set of files for this project"""
501 basedirname = dirname.replace(podir, "", 1)
502 while basedirname.startswith(os.sep):
503 basedirname = basedirname.replace(os.sep, "", 1)
504 ext = os.extsep + poext
505 ponames = [fn for fn in fnames if fn.endswith(ext) and self.languagematch(languagecode, fn[:-len(ext)])]
506 pofilenames.extend([os.path.join(basedirname, poname) for poname in ponames])
508 podir = self.getpodir(languagecode, projectcode)
509 if self.hasgnufiles(podir, languagecode) == "gnu":
510 os.path.walk(podir, addgnufiles, podir)
511 else:
512 pwd = os.path.abspath(os.curdir)
513 os.chdir(podir)
514 os.path.walk(os.curdir, addfiles, None)
515 os.chdir(pwd)
516 return pofilenames
518 def getdefaultrights(self):
519 """Returns the default rights for a logged in user on this Pootle server."""
520 return getattr(self.instance, "defaultrights", "view, suggest, archive, pocompile")
522 def refreshstats(self):
523 """manually refreshes (all or missing) the stats files"""
524 for projectcode in self.getprojectcodes():
525 print "Project %s:" % (projectcode)
526 for languagecode in self.getlanguagecodes(projectcode):
527 print "Project %s, Language %s:" % (projectcode, languagecode)
528 translationproject = self.getproject(languagecode, projectcode)
529 translationproject.stats = {}
530 for pofilename in translationproject.pofilenames:
531 translationproject.getpostats(pofilename)
532 translationproject.pofiles[pofilename] = pootlefile.pootlefile(translationproject, pofilename)
533 print ".",
534 print
535 self.projectcache = {}
537 class DummyPoTree:
538 """A dummy PO tree for testing etc - just treats everything as a single directory"""
539 def __init__(self, podir):
540 self.podirectory = podir
541 def getlanguagename(self, languagecode):
542 return languagecode
543 def getprojectname(self, projectcode):
544 return projectcode
545 def getprojectdescription(self, projectcode):
546 return projectcode
547 def getprojectcheckerstyle(self, projectcode):
548 return ""
549 def getpodir(self, languagecode, projectcode):
550 return self.podirectory
551 def hasgnufiles(self, podir, languagecode):
552 return False
553 def getprojectcreatemofiles(self, projectcode):
554 return False
555 def getpofiles(self, languagecode, projectcode, poext):
556 pofiles = []
557 for dirpath, subdirs, filenames in os.walk(self.podirectory, topdown=False):
558 if dirpath == self.podirectory:
559 subdirpath = ""
560 else:
561 subdirpath = dirpath.replace(self.podirectory+os.path.sep, "", 1)
562 print dirpath, subdirpath, self.podirectory
563 pofiles.extend([os.path.join(subdirpath, name) for name in filenames if name.endswith(poext)])
564 return pofiles
565 def gettemplates(self, projectcode):
566 return []
567 def languagematch(self, languagecode, filename):
568 return True