1
2
3
4 """
5 This file is part of the web2py Web Framework
6 Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8
9 Plural subsystem is created by Vladyslav Kozlovskyy (Ukraine)
10 <dbdevelop@gmail.com>
11 """
12
13 import os
14 import re
15 import sys
16 import pkgutil
17 import logging
18 import marshal
19 from cgi import escape
20 from threading import RLock
21
22 try:
23 import copyreg as copy_reg
24 except ImportError:
25 import copy_reg
26
27 from portalocker import read_locked, LockedFile
28 from utf8 import Utf8
29
30 from fileutils import listdir
31 import settings
32 from cfs import getcfs
33 from html import XML, xmlescape
34 from contrib.markmin.markmin2html import render, markmin_escape
35 from string import maketrans
36
37 __all__ = ['translator', 'findT', 'update_all_languages']
38
39 ostat = os.stat
40 oslistdir = os.listdir
41 pjoin = os.path.join
42 pexists = os.path.exists
43 pdirname = os.path.dirname
44 isdir = os.path.isdir
45 is_gae = False
46
47 DEFAULT_LANGUAGE = 'en'
48 DEFAULT_LANGUAGE_NAME = 'English'
49
50
51
52 DEFAULT_NPLURALS = 1
53
54 DEFAULT_GET_PLURAL_ID = lambda n: 0
55
56 DEFAULT_CONSTRUCT_PLURAL_FORM = lambda word, plural_id: word
57
58 NUMBERS = (int, long, float)
59
60
61 PY_STRING_LITERAL_RE = r'(?<=[^\w]T\()(?P<name>'\
62 + r"[uU]?[rR]?(?:'''(?:[^']|'{1,2}(?!'))*''')|"\
63 + r"(?:'(?:[^'\\]|\\.)*')|" + r'(?:"""(?:[^"]|"{1,2}(?!"))*""")|'\
64 + r'(?:"(?:[^"\\]|\\.)*"))'
65
66 regex_translate = re.compile(PY_STRING_LITERAL_RE, re.DOTALL)
67 regex_param = re.compile(r'{(?P<s>.+?)}')
68
69
70 regex_language = \
71 re.compile('([a-z]{2}(?:\-[a-z]{2})?(?:\-[a-z]{2})?)(?:[,;]|$)')
72 regex_langfile = re.compile('^[a-z]{2}(-[a-z]{2})?\.py$')
73 regex_backslash = re.compile(r"\\([\\{}%])")
74 regex_plural = re.compile('%({.+?})')
75 regex_plural_dict = re.compile('^{(?P<w>[^()[\]][^()[\]]*?)\((?P<n>[^()\[\]]+)\)}$')
76 regex_plural_tuple = re.compile(
77 '^{(?P<w>[^[\]()]+)(?:\[(?P<i>\d+)\])?}$')
78 regex_plural_file = re.compile('^plural-[a-zA-Z]{2}(-[a-zA-Z]{2})?\.py$')
79
80
82 if text.strip():
83 try:
84 import ast
85 return ast.literal_eval(text)
86 except ImportError:
87 return eval(text, {}, {})
88 return None
89
90
91
92
94 def markmin_aux(m):
95 return '{%s}' % markmin_escape(m.group('s'))
96 return render(regex_param.sub(markmin_aux, s),
97 sep='br', autolinks=None, id_prefix='')
98
99
100
101
104
105
108
109
112 ttab_in = maketrans("\\%{}", '\x1c\x1d\x1e\x1f')
113 ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}")
114
115
116
117
118
119
120
121
122
123
124
125 global_language_cache = {}
126
127
143
144
154
155
157 lang_text = read_locked(filename).replace('\r\n', '\n')
158 clear_cache(filename)
159 try:
160 return safe_eval(lang_text) or {}
161 except Exception:
162 e = sys.exc_info()[1]
163 status = 'Syntax error in %s (%s)' % (filename, e)
164 logging.error(status)
165 return {'__corrupted__': status}
166
167
169 """ return dictionary with translation messages
170 """
171 return getcfs('lang:' + filename, filename,
172 lambda: read_dict_aux(filename))
173
174
176 """
177 create list of all possible plural rules files
178 result is cached in PLURAL_RULES dictionary to increase speed
179 """
180 plurals = {}
181 try:
182 import contrib.plural_rules as package
183 for importer, modname, ispkg in pkgutil.iter_modules(package.__path__):
184 if len(modname) == 2:
185 module = __import__(package.__name__ + '.' + modname,
186 fromlist=[modname])
187 lang = modname
188 pname = modname + '.py'
189 nplurals = getattr(module, 'nplurals', DEFAULT_NPLURALS)
190 get_plural_id = getattr(
191 module, 'get_plural_id',
192 DEFAULT_GET_PLURAL_ID)
193 construct_plural_form = getattr(
194 module, 'construct_plural_form',
195 DEFAULT_CONSTRUCT_PLURAL_FORM)
196 plurals[lang] = (lang, nplurals, get_plural_id,
197 construct_plural_form)
198 except ImportError:
199 e = sys.exc_info()[1]
200 logging.warn('Unable to import plural rules: %s' % e)
201 return plurals
202
203 PLURAL_RULES = read_possible_plural_rules()
204
205
207 def get_lang_struct(lang, langcode, langname, langfile_mtime):
208 if lang == 'default':
209 real_lang = langcode.lower()
210 else:
211 real_lang = lang
212 (prules_langcode,
213 nplurals,
214 get_plural_id,
215 construct_plural_form
216 ) = PLURAL_RULES.get(real_lang[:2], ('default',
217 DEFAULT_NPLURALS,
218 DEFAULT_GET_PLURAL_ID,
219 DEFAULT_CONSTRUCT_PLURAL_FORM))
220 if prules_langcode != 'default':
221 (pluraldict_fname,
222 pluraldict_mtime) = plurals.get(real_lang,
223 plurals.get(real_lang[:2],
224 ('plural-%s.py' % real_lang, 0)))
225 else:
226 pluraldict_fname = None
227 pluraldict_mtime = 0
228 return (langcode,
229 langname,
230
231 langfile_mtime,
232 pluraldict_fname,
233 pluraldict_mtime,
234 prules_langcode,
235 nplurals,
236 get_plural_id,
237 construct_plural_form)
238
239 plurals = {}
240 flist = oslistdir(langdir) if isdir(langdir) else []
241
242
243 for pname in flist:
244 if regex_plural_file.match(pname):
245 plurals[pname[7:-3]] = (pname,
246 ostat(pjoin(langdir, pname)).st_mtime)
247 langs = {}
248
249 for fname in flist:
250 if regex_langfile.match(fname) or fname == 'default.py':
251 fname_with_path = pjoin(langdir, fname)
252 d = read_dict(fname_with_path)
253 lang = fname[:-3]
254 langcode = d.get('!langcode!', lang if lang != 'default'
255 else DEFAULT_LANGUAGE)
256 langname = d.get('!langname!', langcode)
257 langfile_mtime = ostat(fname_with_path).st_mtime
258 langs[lang] = get_lang_struct(lang, langcode,
259 langname, langfile_mtime)
260 if 'default' not in langs:
261
262
263 langs['default'] = get_lang_struct('default', DEFAULT_LANGUAGE,
264 DEFAULT_LANGUAGE_NAME, 0)
265 deflang = langs['default']
266 deflangcode = deflang[0]
267 if deflangcode not in langs:
268
269 langs[deflangcode] = deflang[:2] + (0,) + deflang[3:]
270
271 return langs
272
273
277
278
280 lang_text = read_locked(filename).replace('\r\n', '\n')
281 try:
282 return eval(lang_text) or {}
283 except Exception:
284 e = sys.exc_info()[1]
285 status = 'Syntax error in %s (%s)' % (filename, e)
286 logging.error(status)
287 return {'__corrupted__': status}
288
289
293
294
296 if '__corrupted__' in contents:
297 return
298 try:
299 fp = LockedFile(filename, 'w')
300 fp.write('#!/usr/bin/env python\n{\n# "singular form (0)": ["first plural form (1)", "second plural form (2)", ...],\n')
301
302 for key in sorted(contents, lambda x, y: cmp(unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower())):
303 forms = '[' + ','.join([repr(Utf8(form))
304 for form in contents[key]]) + ']'
305 fp.write('%s: %s,\n' % (repr(Utf8(key)), forms))
306 fp.write('}\n')
307 except (IOError, OSError):
308 if not is_gae:
309 logging.warning('Unable to write to file %s' % filename)
310 return
311 finally:
312 fp.close()
313
314
316 if '__corrupted__' in contents:
317 return
318 try:
319 fp = LockedFile(filename, 'w')
320 except (IOError, OSError):
321 if not settings.global_settings.web2py_runtime_gae:
322 logging.warning('Unable to write to file %s' % filename)
323 return
324 fp.write('# coding: utf8\n{\n')
325 for key in sorted(contents, lambda x, y: cmp(unicode(x, 'utf-8').lower(), unicode(y, 'utf-8').lower())):
326 fp.write('%s: %s,\n' % (repr(Utf8(key)), repr(Utf8(contents[key]))))
327 fp.write('}\n')
328 fp.close()
329
330
332 """
333 never to be called explicitly, returned by
334 translator.__call__() or translator.M()
335 """
336 m = s = T = f = t = None
337 M = is_copy = False
338
339 - def __init__(
340 self,
341 message,
342 symbols={},
343 T=None,
344 filter=None,
345 ftag=None,
346 M=False
347 ):
364
366 return "<lazyT %s>" % (repr(Utf8(self.m)), )
367
371
373 return str(self) == str(other)
374
376 return str(self) != str(other)
377
379 return '%s%s' % (self, other)
380
382 return '%s%s' % (other, self)
383
385 return str(self) * other
386
388 return cmp(str(self), str(other))
389
391 return hash(str(self))
392
394 return getattr(str(self), name)
395
398
400 return str(self)[i:j]
401
403 for c in str(self):
404 yield c
405
407 return len(str(self))
408
410 return str(self) if self.M else escape(str(self))
411
413 return str(self).encode(*a, **b)
414
416 return str(self).decode(*a, **b)
417
420
425
426
428 """
429 this class is instantiated by gluon.compileapp.build_environment
430 as the T object
431 ::
432 T.force(None) # turns off translation
433 T.force('fr, it') # forces web2py to translate using fr.py or it.py
434
435 T(\"Hello World\") # translates \"Hello World\" using the selected file
436
437 notice 1: there is no need to force since, by default, T uses
438 http_accept_language to determine a translation file.
439 notice 2:
440 en and en-en are considered different languages!
441 notice 3:
442 if language xx-yy is not found force() probes other similar
443 languages using such algorithm:
444 xx-yy.py -> xx.py -> xx-yy*.py -> xx*.py
445 """
446
447 - def __init__(self, langpath, http_accept_language):
448 self.langpath = langpath
449 self.http_accept_language = http_accept_language
450 self.is_writable = not is_gae
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469 self.set_current_languages()
470 self.lazy = True
471 self.otherTs = {}
472 self.filter = markmin
473 self.ftag = 'markmin'
474
476 """
477 return info for selected language or dictionary with all
478 possible languages info from APP/languages/*.py
479 args:
480 *lang* (str): language
481 returns:
482 if *lang* is defined:
483 return tuple(langcode, langname, langfile_mtime,
484 pluraldict_fname, pluraldict_mtime,
485 prules_langcode, nplurals,
486 get_plural_id, construct_plural_form)
487 or None
488
489 if *lang* is NOT defined:
490 returns dictionary with all possible languages:
491 { langcode(from filename):
492 ( langcode, # language code from !langcode!
493 langname,
494 # language name in national spelling from !langname!
495 langfile_mtime, # m_time of language file
496 pluraldict_fname,# name of plural dictionary file or None (when default.py is not exist)
497 pluraldict_mtime,# m_time of plural dictionary file or 0 if file is not exist
498 prules_langcode, # code of plural rules language or 'default'
499 nplurals, # nplurals for current language
500 get_plural_id, # get_plural_id() for current language
501 construct_plural_form) # construct_plural_form() for current language
502 }
503 """
504 info = read_possible_languages(self.langpath)
505 if lang:
506 info = info.get(lang)
507 return info
508
510 """ get list of all possible languages for current applications """
511 return list(set(self.current_languages +
512 [lang for lang in read_possible_languages(self.langpath).iterkeys()
513 if lang != 'default']))
514
516 """
517 set current AKA "default" languages
518 setting one of this languages makes force() function
519 turn translation off to use default language
520 """
521 if len(languages) == 1 and isinstance(
522 languages[0], (tuple, list)):
523 languages = languages[0]
524 if not languages or languages[0] is None:
525
526 pl_info = self.get_possible_languages_info('default')
527 if pl_info[2] == 0:
528
529 self.default_language_file = self.langpath
530 self.default_t = {}
531 self.current_languages = [DEFAULT_LANGUAGE]
532 else:
533 self.default_language_file = pjoin(self.langpath,
534 'default.py')
535 self.default_t = read_dict(self.default_language_file)
536 self.current_languages = [pl_info[0]]
537 else:
538 self.current_languages = list(languages)
539 self.force(self.http_accept_language)
540
542 """ get plural form of word for number *n*
543 NOTE: *word" MUST be defined in current language
544 (T.accepted_language)
545
546 invoked from T()/T.M() in %%{} tag
547 args:
548 word (str): word in singular
549 n (numeric): number plural form created for
550
551 returns:
552 (str): word in appropriate singular/plural form
553 """
554 if int(n) == 1:
555 return word
556 elif word:
557 id = self.get_plural_id(abs(int(n)))
558
559
560
561
562 if id != 0:
563 forms = self.plural_dict.get(word, [])
564 if len(forms) >= id:
565
566 return forms[id - 1]
567 else:
568
569 forms += [''] * (self.nplurals - len(forms) - 1)
570 form = self.construct_plural_form(word, id)
571 forms[id - 1] = form
572 self.plural_dict[word] = forms
573 if self.is_writable and self.plural_file:
574 write_plural_dict(self.plural_file,
575 self.plural_dict)
576 return form
577 return word
578
579 - def force(self, *languages):
580 """
581
582 select language(s) for translation
583
584 if a list of languages is passed as a parameter,
585 first language from this list that matches the ones
586 from the possible_languages dictionary will be
587 selected
588
589 default language will be selected if none
590 of them matches possible_languages.
591 """
592 pl_info = read_possible_languages(self.langpath)
593
594 def set_plural(language):
595 """
596 initialize plural forms subsystem
597 """
598 lang_info = pl_info.get(language)
599 if lang_info:
600 (pname,
601 pmtime,
602 self.plural_language,
603 self.nplurals,
604 self.get_plural_id,
605 self.construct_plural_form
606 ) = lang_info[3:]
607 pdict = {}
608 if pname:
609 pname = pjoin(self.langpath, pname)
610 if pmtime != 0:
611 pdict = read_plural_dict(pname)
612 self.plural_file = pname
613 self.plural_dict = pdict
614 else:
615 self.plural_language = 'default'
616 self.nplurals = DEFAULT_NPLURALS
617 self.get_plural_id = DEFAULT_GET_PLURAL_ID
618 self.construct_plural_form = DEFAULT_CONSTRUCT_PLURAL_FORM
619 self.plural_file = None
620 self.plural_dict = {}
621 language = ''
622 if len(languages) == 1 and isinstance(languages[0], str):
623 languages = regex_language.findall(languages[0].lower())
624 elif not languages or languages[0] is None:
625 languages = []
626 self.requested_languages = languages = tuple(languages)
627 if languages:
628 all_languages = set(lang for lang in pl_info.iterkeys()
629 if lang != 'default') \
630 | set(self.current_languages)
631 for lang in languages:
632
633
634
635 lang5 = lang[:5]
636 if lang5 in all_languages:
637 language = lang5
638 else:
639 lang2 = lang[:2]
640 if len(lang5) > 2 and lang2 in all_languages:
641 language = lang2
642 else:
643 for l in all_languages:
644 if l[:2] == lang2:
645 language = l
646 if language:
647 if language in self.current_languages:
648 break
649 self.language_file = pjoin(self.langpath, language + '.py')
650 self.t = read_dict(self.language_file)
651 self.cache = global_language_cache.setdefault(
652 self.language_file,
653 ({}, RLock()))
654 set_plural(language)
655 self.accepted_language = language
656 return languages
657 self.accepted_language = language or self.current_languages[0]
658 self.language_file = self.default_language_file
659 self.cache = global_language_cache.setdefault(self.language_file,
660 ({}, RLock()))
661 self.t = self.default_t
662 set_plural(self.accepted_language)
663 return languages
664
665 - def __call__(self, message, symbols={}, language=None, lazy=None):
666 """
667 get cached translated plain text message with inserted parameters(symbols)
668 if lazy==True lazyT object is returned
669 """
670 if lazy is None:
671 lazy = self.lazy
672 if not language:
673 if lazy:
674 return lazyT(message, symbols, self)
675 else:
676 return self.translate(message, symbols)
677 else:
678 try:
679 otherT = self.otherTs[language]
680 except KeyError:
681 otherT = self.otherTs[language] = translator(
682 self.langpath, self.http_accept_language)
683 otherT.force(language)
684 return otherT(message, symbols, lazy=lazy)
685
686 - def apply_filter(self, message, symbols={}, filter=None, ftag=None):
687 def get_tr(message, prefix, filter):
688 s = self.get_t(message, prefix)
689 return filter(s) if filter else self.filter(s)
690 if filter:
691 prefix = '@' + (ftag or 'userdef') + '\x01'
692 else:
693 prefix = '@' + self.ftag + '\x01'
694 message = get_from_cache(
695 self.cache, prefix + message,
696 lambda: get_tr(message, prefix, filter))
697 if symbols or symbols == 0 or symbols == "":
698 if isinstance(symbols, dict):
699 symbols.update(
700 (key, xmlescape(value).translate(ttab_in))
701 for key, value in symbols.iteritems()
702 if not isinstance(value, NUMBERS))
703 else:
704 if not isinstance(symbols, tuple):
705 symbols = (symbols,)
706 symbols = tuple(
707 value if isinstance(value, NUMBERS)
708 else xmlescape(value).translate(ttab_in)
709 for value in symbols)
710 message = self.params_substitution(message, symbols)
711 return XML(message.translate(ttab_out))
712
713 - def M(self, message, symbols={}, language=None,
714 lazy=None, filter=None, ftag=None):
715 """
716 get cached translated markmin-message with inserted parametes
717 if lazy==True lazyT object is returned
718 """
719 if lazy is None:
720 lazy = self.lazy
721 if not language:
722 if lazy:
723 return lazyT(message, symbols, self, filter, ftag, True)
724 else:
725 return self.apply_filter(message, symbols, filter, ftag)
726 else:
727 try:
728 otherT = self.otherTs[language]
729 except KeyError:
730 otherT = self.otherTs[language] = translator(self.request)
731 otherT.force(language)
732 return otherT.M(message, symbols, lazy=lazy)
733
734 - def get_t(self, message, prefix=''):
735 """
736 user ## to add a comment into a translation string
737 the comment can be useful do discriminate different possible
738 translations for the same string (for example different locations)
739
740 T(' hello world ') -> ' hello world '
741 T(' hello world ## token') -> ' hello world '
742 T('hello ## world## token') -> 'hello ## world'
743
744 the ## notation is ignored in multiline strings and strings that
745 start with ##. this is to allow markmin syntax to be translated
746 """
747 if isinstance(message, unicode):
748 message = message.encode('utf8')
749 if isinstance(prefix, unicode):
750 prefix = prefix.encode('utf8')
751 key = prefix + message
752 mt = self.t.get(key, None)
753 if mt is not None:
754 return mt
755
756 if message.find('##') > 0 and not '\n' in message:
757
758 message = message.rsplit('##', 1)[0]
759
760 self.t[key] = mt = self.default_t.get(key, message)
761
762 if self.is_writable and self.language_file != self.default_language_file:
763 write_dict(self.language_file, self.t)
764 return regex_backslash.sub(
765 lambda m: m.group(1).translate(ttab_in), mt)
766
768 """
769 substitute parameters from symbols into message using %.
770 also parse %%{} placeholders for plural-forms processing.
771 returns: string with parameters
772 NOTE: *symbols* MUST BE OR tuple OR dict of parameters!
773 """
774 def sub_plural(m):
775 """string in %{} is transformed by this rules:
776 If string starts with \\, ! or ? such transformations
777 take place:
778
779 "!string of words" -> "String of word" (Capitalize)
780 "!!string of words" -> "String Of Word" (Title)
781 "!!!string of words" -> "STRING OF WORD" (Upper)
782 "\\!string of words" -> "!string of word"
783 (remove \\ and disable transformations)
784 "?word?number" -> "word" (return word, if number == 1)
785 "?number" or "??number" -> "" (remove number,
786 if number == 1)
787 "?word?number" -> "number" (if number != 1)
788 """
789 def sub_tuple(m):
790 """ word[number], !word[number], !!word[number], !!!word[number]
791 word, !word, !!word, !!!word, ?word?number, ??number, ?number
792 ?word?word[number], ?word?[number], ??word[number]
793 """
794 w, i = m.group('w', 'i')
795 c = w[0]
796 if c not in '!?':
797 return self.plural(w, symbols[int(i or 0)])
798 elif c == '?':
799 (p1, sep, p2) = w[1:].partition("?")
800 part1 = p1 if sep else ""
801 (part2, sep, part3) = (p2 if sep else p1).partition("?")
802 if not sep:
803 part3 = part2
804 if i is None:
805
806 if not part2:
807 return m.group(0)
808 num = int(part2)
809 else:
810
811 num = int(symbols[int(i or 0)])
812 return part1 if num == 1 else part3 if num == 0 else part2
813 elif w.startswith('!!!'):
814 word = w[3:]
815 fun = upper_fun
816 elif w.startswith('!!'):
817 word = w[2:]
818 fun = title_fun
819 else:
820 word = w[1:]
821 fun = cap_fun
822 if i is not None:
823 return fun(self.plural(word, symbols[int(i)]))
824 return fun(word)
825
826 def sub_dict(m):
827 """ word(var), !word(var), !!word(var), !!!word(var)
828 word(num), !word(num), !!word(num), !!!word(num)
829 ?word2(var), ?word1?word2(var), ?word1?word2?word0(var)
830 ?word2(num), ?word1?word2(num), ?word1?word2?word0(num)
831 """
832 w, n = m.group('w', 'n')
833 c = w[0]
834 n = int(n) if n.isdigit() else symbols[n]
835 if c not in '!?':
836 return self.plural(w, n)
837 elif c == '?':
838
839 (p1, sep, p2) = w[1:].partition("?")
840 part1 = p1 if sep else ""
841 (part2, sep, part3) = (p2 if sep else p1).partition("?")
842 if not sep:
843 part3 = part2
844 num = int(n)
845 return part1 if num == 1 else part3 if num == 0 else part2
846 elif w.startswith('!!!'):
847 word = w[3:]
848 fun = upper_fun
849 elif w.startswith('!!'):
850 word = w[2:]
851 fun = title_fun
852 else:
853 word = w[1:]
854 fun = cap_fun
855 return fun(self.plural(word, n))
856
857 s = m.group(1)
858 part = regex_plural_tuple.sub(sub_tuple, s)
859 if part == s:
860 part = regex_plural_dict.sub(sub_dict, s)
861 if part == s:
862 return m.group(0)
863 return part
864 message = message % symbols
865 message = regex_plural.sub(sub_plural, message)
866 return message
867
869 """
870 get cached translated message with inserted parameters(symbols)
871 """
872 message = get_from_cache(self.cache, message,
873 lambda: self.get_t(message))
874 if symbols or symbols == 0 or symbols == "":
875 if isinstance(symbols, dict):
876 symbols.update(
877 (key, str(value).translate(ttab_in))
878 for key, value in symbols.iteritems()
879 if not isinstance(value, NUMBERS))
880 else:
881 if not isinstance(symbols, tuple):
882 symbols = (symbols,)
883 symbols = tuple(
884 value if isinstance(value, NUMBERS)
885 else str(value).translate(ttab_in)
886 for value in symbols)
887 message = self.params_substitution(message, symbols)
888 return message.translate(ttab_out)
889
890
892 """
893 must be run by the admin app
894 """
895 lang_file = pjoin(path, 'languages', language + '.py')
896 sentences = read_dict(lang_file)
897 mp = pjoin(path, 'models')
898 cp = pjoin(path, 'controllers')
899 vp = pjoin(path, 'views')
900 mop = pjoin(path, 'modules')
901 for filename in \
902 listdir(mp, '^.+\.py$', 0) + listdir(cp, '^.+\.py$', 0)\
903 + listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0):
904 data = read_locked(filename)
905 items = regex_translate.findall(data)
906 for item in items:
907 try:
908 message = safe_eval(item)
909 except:
910 continue
911 if not message.startswith('#') and not '\n' in message:
912 tokens = message.rsplit('##', 1)
913 else:
914
915 tokens = [message]
916 if len(tokens) == 2:
917 message = tokens[0].strip() + '##' + tokens[1].strip()
918 if message and not message in sentences:
919 sentences[message] = message
920 if not '!langcode!' in sentences:
921 sentences['!langcode!'] = (
922 DEFAULT_LANGUAGE if language in ('default', DEFAULT_LANGUAGE) else language)
923 if not '!langname!' in sentences:
924 sentences['!langname!'] = (
925 DEFAULT_LANGUAGE_NAME if language in ('default', DEFAULT_LANGUAGE)
926 else sentences['!langcode!'])
927 write_dict(lang_file, sentences)
928
929
930
931
933 return marshal.loads(data)
934
935
938 copy_reg.pickle(lazyT, lazyT_pickle, lazyT_unpickle)
939
940
942 path = pjoin(application_path, 'languages/')
943 for language in oslistdir(path):
944 if regex_langfile.match(language):
945 findT(application_path, language[:-3])
946
947
948 if __name__ == '__main__':
949 import doctest
950 doctest.testmod()
951