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