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