Package Dropbox :: Package web2py :: Package gluon :: Module languages
[hide private]
[frames] | no frames]

Source Code for Module Dropbox.web2py.gluon.languages

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  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 # python 3 
 24  except ImportError: 
 25      import copy_reg # python 2 
 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 # settings.global_settings.web2py_runtime_gae 
 46   
 47  DEFAULT_LANGUAGE = 'en' 
 48  DEFAULT_LANGUAGE_NAME = 'English' 
 49   
 50  # DEFAULT PLURAL-FORMS RULES: 
 51  # language doesn't use plural forms 
 52  DEFAULT_NPLURALS = 1 
 53  # only one singular/plural form is used 
 54  DEFAULT_GET_PLURAL_ID = lambda n: 0 
 55  # word is unchangeable 
 56  DEFAULT_CONSTRUCT_PLURAL_FORM = lambda word, plural_id: word 
 57   
 58  NUMBERS = (int, long, float) 
 59   
 60  # pattern to find T(blah blah blah) expressions 
 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  # pattern for a valid accept_language 
 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>[^()\[\]]+)\)}$')  # %%{word(varname or number)} 
 76  regex_plural_tuple = re.compile( 
 77      '^{(?P<w>[^[\]()]+)(?:\[(?P<i>\d+)\])?}$')  # %%{word[index]} or %%{word} 
 78  regex_plural_file = re.compile('^plural-[a-zA-Z]{2}(-[a-zA-Z]{2})?\.py$') 
 79   
 80   
81 -def safe_eval(text):
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 # used as default filter in translator.M() 91 92
93 -def markmin(s):
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 # UTF8 helper functions 100 101
102 -def upper_fun(s):
103 return unicode(s, 'utf-8').upper().encode('utf-8')
104 105
106 -def title_fun(s):
107 return unicode(s, 'utf-8').title().encode('utf-8')
108 109
110 -def cap_fun(s):
111 return unicode(s, 'utf-8').capitalize().encode('utf-8')
112 ttab_in = maketrans("\\%{}", '\x1c\x1d\x1e\x1f') 113 ttab_out = maketrans('\x1c\x1d\x1e\x1f', "\\%{}") 114 115 # cache of translated messages: 116 # global_language_cache: 117 # { 'languages/xx.py': 118 # ( {"def-message": "xx-message", 119 # ... 120 # "def-message": "xx-message"}, lock_object ) 121 # 'languages/yy.py': ( {dict}, lock_object ) 122 # ... 123 # } 124 125 global_language_cache = {} 126 127
128 -def get_from_cache(cache, val, fun):
129 lang_dict, lock = cache 130 lock.acquire() 131 try: 132 result = lang_dict.get(val) 133 finally: 134 lock.release() 135 if result: 136 return result 137 lock.acquire() 138 try: 139 result = lang_dict.setdefault(val, fun()) 140 finally: 141 lock.release() 142 return result
143 144
145 -def clear_cache(filename):
146 cache = global_language_cache.setdefault( 147 filename, ({}, RLock())) 148 lang_dict, lock = cache 149 lock.acquire() 150 try: 151 lang_dict.clear() 152 finally: 153 lock.release()
154 155
156 -def read_dict_aux(filename):
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
168 -def read_dict(filename):
169 """ return dictionary with translation messages 170 """ 171 return getcfs('lang:' + filename, filename, 172 lambda: read_dict_aux(filename))
173 174
175 -def read_possible_plural_rules():
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
206 -def read_possible_languages_aux(langdir):
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, # language code from !langcode! 229 langname, 230 # language name in national spelling from !langname! 231 langfile_mtime, # m_time of language file 232 pluraldict_fname, # name of plural dictionary file or None (when default.py is not exist) 233 pluraldict_mtime, # m_time of plural dictionary file or 0 if file is not exist 234 prules_langcode, # code of plural rules language or 'default' 235 nplurals, # nplurals for current language 236 get_plural_id, # get_plural_id() for current language 237 construct_plural_form) # construct_plural_form() for current language
238 239 plurals = {} 240 flist = oslistdir(langdir) 241 # scan languages directory for plural dict files: 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 # scan languages directory for langfiles: 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 # if default.py is not found, 261 # add DEFAULT_LANGUAGE as default language: 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 # create language from default.py: 268 langs[deflangcode] = deflang[:2] + (0,) + deflang[3:] 269 270 return langs 271 272
273 -def read_possible_languages(langpath):
274 return getcfs('langs:' + langpath, langpath, 275 lambda: read_possible_languages_aux(langpath))
276 277
278 -def read_plural_dict_aux(filename):
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
289 -def read_plural_dict(filename):
290 return getcfs('plurals:' + filename, filename, 291 lambda: read_plural_dict_aux(filename))
292 293
294 -def write_plural_dict(filename, contents):
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 # coding: utf8\n{\n') 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
314 -def write_dict(filename, contents):
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
330 -class lazyT(object):
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 ):
347 if isinstance(message, lazyT): 348 self.m = message.m 349 self.s = message.s 350 self.T = message.T 351 self.f = message.f 352 self.t = message.t 353 self.M = message.M 354 self.is_copy = True 355 else: 356 self.m = message 357 self.s = symbols 358 self.T = T 359 self.f = filter 360 self.t = ftag 361 self.M = M 362 self.is_copy = False
363
364 - def __repr__(self):
365 return "<lazyT %s>" % (repr(Utf8(self.m)), )
366
367 - def __str__(self):
368 return str(self.T.apply_filter(self.m, self.s, self.f, self.t) if self.M else 369 self.T.translate(self.m, self.s))
370
371 - def __eq__(self, other):
372 return str(self) == str(other)
373
374 - def __ne__(self, other):
375 return str(self) != str(other)
376
377 - def __add__(self, other):
378 return '%s%s' % (self, other)
379
380 - def __radd__(self, other):
381 return '%s%s' % (other, self)
382
383 - def __mul__(self, other):
384 return str(self) * other
385
386 - def __cmp__(self, other):
387 return cmp(str(self), str(other))
388
389 - def __hash__(self):
390 return hash(str(self))
391
392 - def __getattr__(self, name):
393 return getattr(str(self), name)
394
395 - def __getitem__(self, i):
396 return str(self)[i]
397
398 - def __getslice__(self, i, j):
399 return str(self)[i:j]
400
401 - def __iter__(self):
402 for c in str(self): 403 yield c
404
405 - def __len__(self):
406 return len(str(self))
407
408 - def xml(self):
409 return str(self) if self.M else escape(str(self))
410
411 - def encode(self, *a, **b):
412 return str(self).encode(*a, **b)
413
414 - def decode(self, *a, **b):
415 return str(self).decode(*a, **b)
416
417 - def read(self):
418 return str(self)
419
420 - def __mod__(self, symbols):
421 if self.is_copy: 422 return lazyT(self) 423 return lazyT(self.m, symbols, self.T, self.f, self.t, self.M)
424 425
426 -class translator(object):
427 """ 428 this class is instantiated by gluon.compileapp.build_environment 429 as the T object 430 :: 431 T.force(None) # turns off translation 432 T.force('fr, it') # forces web2py to translate using fr.py or it.py 433 434 T(\"Hello World\") # translates \"Hello World\" using the selected file 435 436 notice 1: there is no need to force since, by default, T uses 437 http_accept_language to determine a translation file. 438 notice 2: 439 en and en-en are considered different languages! 440 notice 3: 441 if language xx-yy is not found force() probes other similar 442 languages using such algorithm: 443 xx-yy.py -> xx.py -> xx-yy*.py -> xx*.py 444 """ 445
446 - def __init__(self, langpath, http_accept_language):
447 self.langpath = langpath 448 self.http_accept_language = http_accept_language 449 self.is_writable = not is_gae 450 # filled in self.force(): 451 #------------------------ 452 # self.cache 453 # self.accepted_language 454 # self.language_file 455 # self.plural_language 456 # self.nplurals 457 # self.get_plural_id 458 # self.construct_plural_form 459 # self.plural_file 460 # self.plural_dict 461 # self.requested_languages 462 #---------------------------------------- 463 # filled in self.set_current_languages(): 464 #---------------------------------------- 465 # self.default_language_file 466 # self.default_t 467 # self.current_languages 468 self.set_current_languages() 469 self.lazy = True 470 self.otherTs = {} 471 self.filter = markmin 472 self.ftag = 'markmin'
473
474 - def get_possible_languages_info(self, lang=None):
475 """ 476 return info for selected language or dictionary with all 477 possible languages info from APP/languages/*.py 478 args: 479 *lang* (str): language 480 returns: 481 if *lang* is defined: 482 return tuple(langcode, langname, langfile_mtime, 483 pluraldict_fname, pluraldict_mtime, 484 prules_langcode, nplurals, 485 get_plural_id, construct_plural_form) 486 or None 487 488 if *lang* is NOT defined: 489 returns dictionary with all possible languages: 490 { langcode(from filename): 491 ( langcode, # language code from !langcode! 492 langname, 493 # language name in national spelling from !langname! 494 langfile_mtime, # m_time of language file 495 pluraldict_fname,# name of plural dictionary file or None (when default.py is not exist) 496 pluraldict_mtime,# m_time of plural dictionary file or 0 if file is not exist 497 prules_langcode, # code of plural rules language or 'default' 498 nplurals, # nplurals for current language 499 get_plural_id, # get_plural_id() for current language 500 construct_plural_form) # construct_plural_form() for current language 501 } 502 """ 503 info = read_possible_languages(self.langpath) 504 if lang: 505 info = info.get(lang) 506 return info
507
508 - def get_possible_languages(self):
509 """ get list of all possible languages for current applications """ 510 return list(set(self.current_languages + 511 [lang for lang in read_possible_languages(self.langpath).iterkeys() 512 if lang != 'default']))
513
514 - def set_current_languages(self, *languages):
515 """ 516 set current AKA "default" languages 517 setting one of this languages makes force() function 518 turn translation off to use default language 519 """ 520 if len(languages) == 1 and isinstance( 521 languages[0], (tuple, list)): 522 languages = languages[0] 523 if not languages or languages[0] is None: 524 # set default language from default.py/DEFAULT_LANGUAGE 525 pl_info = self.get_possible_languages_info('default') 526 if pl_info[2] == 0: # langfile_mtime 527 # if languages/default.py is not found 528 self.default_language_file = self.langpath 529 self.default_t = {} 530 self.current_languages = [DEFAULT_LANGUAGE] 531 else: 532 self.default_language_file = pjoin(self.langpath, 533 'default.py') 534 self.default_t = read_dict(self.default_language_file) 535 self.current_languages = [pl_info[0]] # !langcode! 536 else: 537 self.current_languages = list(languages) 538 self.force(self.http_accept_language)
539
540 - def plural(self, word, n):
541 """ get plural form of word for number *n* 542 NOTE: *word" MUST be defined in current language 543 (T.accepted_language) 544 545 invoked from T()/T.M() in %%{} tag 546 args: 547 word (str): word in singular 548 n (numeric): number plural form created for 549 550 returns: 551 (str): word in appropriate singular/plural form 552 """ 553 if int(n) == 1: 554 return word 555 elif word: 556 id = self.get_plural_id(abs(int(n))) 557 # id = 0 singular form 558 # id = 1 first plural form 559 # id = 2 second plural form 560 # etc. 561 if id != 0: 562 forms = self.plural_dict.get(word, []) 563 if len(forms) >= id: 564 # have this plural form: 565 return forms[id - 1] 566 else: 567 # guessing this plural form 568 forms += [''] * (self.nplurals - len(forms) - 1) 569 form = self.construct_plural_form(word, id) 570 forms[id - 1] = form 571 self.plural_dict[word] = forms 572 if self.is_writable and self.plural_file: 573 write_plural_dict(self.plural_file, 574 self.plural_dict) 575 return form 576 return word
577
578 - def force(self, *languages):
579 """ 580 581 select language(s) for translation 582 583 if a list of languages is passed as a parameter, 584 first language from this list that matches the ones 585 from the possible_languages dictionary will be 586 selected 587 588 default language will be selected if none 589 of them matches possible_languages. 590 """ 591 pl_info = read_possible_languages(self.langpath) 592 593 def set_plural(language): 594 """ 595 initialize plural forms subsystem 596 """ 597 lang_info = pl_info.get(language) 598 if lang_info: 599 (pname, 600 pmtime, 601 self.plural_language, 602 self.nplurals, 603 self.get_plural_id, 604 self.construct_plural_form 605 ) = lang_info[3:] 606 pdict = {} 607 if pname: 608 pname = pjoin(self.langpath, pname) 609 if pmtime != 0: 610 pdict = read_plural_dict(pname) 611 self.plural_file = pname 612 self.plural_dict = pdict 613 else: 614 self.plural_language = 'default' 615 self.nplurals = DEFAULT_NPLURALS 616 self.get_plural_id = DEFAULT_GET_PLURAL_ID 617 self.construct_plural_form = DEFAULT_CONSTRUCT_PLURAL_FORM 618 self.plural_file = None 619 self.plural_dict = {}
620 language = '' 621 if len(languages) == 1 and isinstance(languages[0], str): 622 languages = regex_language.findall(languages[0].lower()) 623 elif not languages or languages[0] is None: 624 languages = [] 625 self.requested_languages = languages = tuple(languages) 626 if languages: 627 all_languages = set(lang for lang in pl_info.iterkeys() 628 if lang != 'default') \ 629 | set(self.current_languages) 630 for lang in languages: 631 # compare "aa-bb" | "aa" from *language* parameter 632 # with strings from langlist using such alghorythm: 633 # xx-yy.py -> xx.py -> xx*.py 634 lang5 = lang[:5] 635 if lang5 in all_languages: 636 language = lang5 637 else: 638 lang2 = lang[:2] 639 if len(lang5) > 2 and lang2 in all_languages: 640 language = lang2 641 else: 642 for l in all_languages: 643 if l[:2] == lang2: 644 language = l 645 if language: 646 if language in self.current_languages: 647 break 648 self.language_file = pjoin(self.langpath, language + '.py') 649 self.t = read_dict(self.language_file) 650 self.cache = global_language_cache.setdefault( 651 self.language_file, 652 ({}, RLock())) 653 set_plural(language) 654 self.accepted_language = language 655 return languages 656 self.accepted_language = language or self.current_languages[0] 657 self.language_file = self.default_language_file 658 self.cache = global_language_cache.setdefault(self.language_file, 659 ({}, RLock())) 660 self.t = self.default_t 661 set_plural(self.accepted_language) 662 return languages
663
664 - def __call__(self, message, symbols={}, language=None, lazy=None):
665 """ 666 get cached translated plain text message with inserted parameters(symbols) 667 if lazy==True lazyT object is returned 668 """ 669 if lazy is None: 670 lazy = self.lazy 671 if not language: 672 if lazy: 673 return lazyT(message, symbols, self) 674 else: 675 return self.translate(message, symbols) 676 else: 677 try: 678 otherT = self.otherTs[language] 679 except KeyError: 680 otherT = self.otherTs[language] = translator( 681 self.langpath, self.http_accept_language) 682 otherT.force(language) 683 return otherT(message, symbols, lazy=lazy)
684
685 - def apply_filter(self, message, symbols={}, filter=None, ftag=None):
686 def get_tr(message, prefix, filter): 687 s = self.get_t(message, prefix) 688 return filter(s) if filter else self.filter(s)
689 if filter: 690 prefix = '@' + (ftag or 'userdef') + '\x01' 691 else: 692 prefix = '@' + self.ftag + '\x01' 693 message = get_from_cache( 694 self.cache, prefix + message, 695 lambda: get_tr(message, prefix, filter)) 696 if symbols or symbols == 0 or symbols == "": 697 if isinstance(symbols, dict): 698 symbols.update( 699 (key, xmlescape(value).translate(ttab_in)) 700 for key, value in symbols.iteritems() 701 if not isinstance(value, NUMBERS)) 702 else: 703 if not isinstance(symbols, tuple): 704 symbols = (symbols,) 705 symbols = tuple( 706 value if isinstance(value, NUMBERS) 707 else xmlescape(value).translate(ttab_in) 708 for value in symbols) 709 message = self.params_substitution(message, symbols) 710 return XML(message.translate(ttab_out)) 711
712 - def M(self, message, symbols={}, language=None, 713 lazy=None, filter=None, ftag=None):
714 """ 715 get cached translated markmin-message with inserted parametes 716 if lazy==True lazyT object is returned 717 """ 718 if lazy is None: 719 lazy = self.lazy 720 if not language: 721 if lazy: 722 return lazyT(message, symbols, self, filter, ftag, True) 723 else: 724 return self.apply_filter(message, symbols, filter, ftag) 725 else: 726 try: 727 otherT = self.otherTs[language] 728 except KeyError: 729 otherT = self.otherTs[language] = translator(self.request) 730 otherT.force(language) 731 return otherT.M(message, symbols, lazy=lazy)
732
733 - def get_t(self, message, prefix=''):
734 """ 735 user ## to add a comment into a translation string 736 the comment can be useful do discriminate different possible 737 translations for the same string (for example different locations) 738 739 T(' hello world ') -> ' hello world ' 740 T(' hello world ## token') -> ' hello world ' 741 T('hello ## world## token') -> 'hello ## world' 742 743 the ## notation is ignored in multiline strings and strings that 744 start with ##. this is to allow markmin syntax to be translated 745 """ 746 if isinstance(message, unicode): 747 message = message.encode('utf8') 748 if isinstance(prefix, unicode): 749 prefix = prefix.encode('utf8') 750 key = prefix + message 751 mt = self.t.get(key, None) 752 if mt is not None: 753 return mt 754 # we did not find a translation 755 if message.find('##') > 0 and not '\n' in message: 756 # remove comments 757 message = message.rsplit('##', 1)[0] 758 # guess translation same as original 759 self.t[key] = mt = self.default_t.get(key, message) 760 # update language file for latter translation 761 if self.is_writable and self.language_file != self.default_language_file: 762 write_dict(self.language_file, self.t) 763 return regex_backslash.sub( 764 lambda m: m.group(1).translate(ttab_in), mt)
765
766 - def params_substitution(self, message, symbols):
767 """ 768 substitute parameters from symbols into message using %. 769 also parse %%{} placeholders for plural-forms processing. 770 returns: string with parameters 771 NOTE: *symbols* MUST BE OR tuple OR dict of parameters! 772 """ 773 def sub_plural(m): 774 """string in %{} is transformed by this rules: 775 If string starts with \\, ! or ? such transformations 776 take place: 777 778 "!string of words" -> "String of word" (Capitalize) 779 "!!string of words" -> "String Of Word" (Title) 780 "!!!string of words" -> "STRING OF WORD" (Upper) 781 "\\!string of words" -> "!string of word" 782 (remove \\ and disable transformations) 783 "?word?number" -> "word" (return word, if number == 1) 784 "?number" or "??number" -> "" (remove number, 785 if number == 1) 786 "?word?number" -> "number" (if number != 1) 787 """ 788 def sub_tuple(m): 789 """ word[number], !word[number], !!word[number], !!!word[number] 790 word, !word, !!word, !!!word, ?word?number, ??number, ?number 791 ?word?word[number], ?word?[number], ??word[number] 792 """ 793 w, i = m.group('w', 'i') 794 c = w[0] 795 if c not in '!?': 796 return self.plural(w, symbols[int(i or 0)]) 797 elif c == '?': 798 (p1, sep, p2) = w[1:].partition("?") 799 part1 = p1 if sep else "" 800 (part2, sep, part3) = (p2 if sep else p1).partition("?") 801 if not sep: 802 part3 = part2 803 if i is None: 804 # ?[word]?number[?number] or ?number 805 if not part2: 806 return m.group(0) 807 num = int(part2) 808 else: 809 # ?[word]?word2[?word3][number] 810 num = int(symbols[int(i or 0)]) 811 return part1 if num == 1 else part3 if num == 0 else part2 812 elif w.startswith('!!!'): 813 word = w[3:] 814 fun = upper_fun 815 elif w.startswith('!!'): 816 word = w[2:] 817 fun = title_fun 818 else: 819 word = w[1:] 820 fun = cap_fun 821 if i is not None: 822 return fun(self.plural(word, symbols[int(i)])) 823 return fun(word)
824 825 def sub_dict(m): 826 """ word(var), !word(var), !!word(var), !!!word(var) 827 word(num), !word(num), !!word(num), !!!word(num) 828 ?word2(var), ?word1?word2(var), ?word1?word2?word0(var) 829 ?word2(num), ?word1?word2(num), ?word1?word2?word0(num) 830 """ 831 w, n = m.group('w', 'n') 832 c = w[0] 833 n = int(n) if n.isdigit() else symbols[n] 834 if c not in '!?': 835 return self.plural(w, n) 836 elif c == '?': 837 # ?[word1]?word2[?word0](var or num), ?[word1]?word2(var or num) or ?word2(var or num) 838 (p1, sep, p2) = w[1:].partition("?") 839 part1 = p1 if sep else "" 840 (part2, sep, part3) = (p2 if sep else p1).partition("?") 841 if not sep: 842 part3 = part2 843 num = int(n) 844 return part1 if num == 1 else part3 if num == 0 else part2 845 elif w.startswith('!!!'): 846 word = w[3:] 847 fun = upper_fun 848 elif w.startswith('!!'): 849 word = w[2:] 850 fun = title_fun 851 else: 852 word = w[1:] 853 fun = cap_fun 854 return fun(self.plural(word, n)) 855 856 s = m.group(1) 857 part = regex_plural_tuple.sub(sub_tuple, s) 858 if part == s: 859 part = regex_plural_dict.sub(sub_dict, s) 860 if part == s: 861 return m.group(0) 862 return part 863 message = message % symbols 864 message = regex_plural.sub(sub_plural, message) 865 return message 866
867 - def translate(self, message, symbols):
868 """ 869 get cached translated message with inserted parameters(symbols) 870 """ 871 message = get_from_cache(self.cache, message, 872 lambda: self.get_t(message)) 873 if symbols or symbols == 0 or symbols == "": 874 if isinstance(symbols, dict): 875 symbols.update( 876 (key, str(value).translate(ttab_in)) 877 for key, value in symbols.iteritems() 878 if not isinstance(value, NUMBERS)) 879 else: 880 if not isinstance(symbols, tuple): 881 symbols = (symbols,) 882 symbols = tuple( 883 value if isinstance(value, NUMBERS) 884 else str(value).translate(ttab_in) 885 for value in symbols) 886 message = self.params_substitution(message, symbols) 887 return message.translate(ttab_out)
888 889
890 -def findT(path, language=DEFAULT_LANGUAGE):
891 """ 892 must be run by the admin app 893 """ 894 lang_file = pjoin(path, 'languages', language + '.py') 895 sentences = read_dict(lang_file) 896 mp = pjoin(path, 'models') 897 cp = pjoin(path, 'controllers') 898 vp = pjoin(path, 'views') 899 mop = pjoin(path, 'modules') 900 for filename in \ 901 listdir(mp, '^.+\.py$', 0) + listdir(cp, '^.+\.py$', 0)\ 902 + listdir(vp, '^.+\.html$', 0) + listdir(mop, '^.+\.py$', 0): 903 data = read_locked(filename) 904 items = regex_translate.findall(data) 905 for item in items: 906 try: 907 message = safe_eval(item) 908 except: 909 continue # silently ignore inproperly formatted strings 910 if not message.startswith('#') and not '\n' in message: 911 tokens = message.rsplit('##', 1) 912 else: 913 # this allows markmin syntax in translations 914 tokens = [message] 915 if len(tokens) == 2: 916 message = tokens[0].strip() + '##' + tokens[1].strip() 917 if message and not message in sentences: 918 sentences[message] = message 919 if not '!langcode!' in sentences: 920 sentences['!langcode!'] = ( 921 DEFAULT_LANGUAGE if language in ('default', DEFAULT_LANGUAGE) else language) 922 if not '!langname!' in sentences: 923 sentences['!langname!'] = ( 924 DEFAULT_LANGUAGE_NAME if language in ('default', DEFAULT_LANGUAGE) 925 else sentences['!langcode!']) 926 write_dict(lang_file, sentences)
927 928 ### important to allow safe session.flash=T(....) 929 930
931 -def lazyT_unpickle(data):
932 return marshal.loads(data)
933 934
935 -def lazyT_pickle(data):
936 return lazyT_unpickle, (marshal.dumps(str(data)),)
937 copy_reg.pickle(lazyT, lazyT_pickle, lazyT_unpickle) 938 939
940 -def update_all_languages(application_path):
941 path = pjoin(application_path, 'languages/') 942 for language in oslistdir(path): 943 if regex_langfile.match(language): 944 findT(application_path, language[:-3])
945 946 947 if __name__ == '__main__': 948 import doctest 949 doctest.testmod() 950