1# -*- coding: utf-8 -*- 2""" 3 webapp2_extras.i18n 4 =================== 5 6 Internationalization support for webapp2. 7 8 Several ideas borrowed from tipfy.i18n and Flask-Babel. 9 10 :copyright: 2011 by tipfy.org. 11 :license: Apache Sotware License, see LICENSE for details. 12""" 13import datetime 14import gettext as gettext_stdlib 15 16import babel 17from babel import dates 18from babel import numbers 19from babel import support 20 21try: 22 # Monkeypatches pytz for gae. 23 import pytz.gae 24except ImportError: # pragma: no cover 25 pass 26 27import pytz 28 29import webapp2 30 31#: Default configuration values for this module. Keys are: 32#: 33#: translations_path 34#: Path to the translations directory. Default is `locale`. 35#: 36#: domains 37#: List of gettext domains to be used. Default is ``['messages']``. 38#: 39#: default_locale 40#: A locale code to be used as fallback. Default is ``'en_US'``. 41#: 42#: default_timezone 43#: The application default timezone according to the Olson 44#: database. Default is ``'UTC'``. 45#: 46#: locale_selector 47#: A function that receives (store, request) and returns a locale 48#: to be used for a request. If not defined, uses `default_locale`. 49#: Can also be a string in dotted notation to be imported. 50#: 51#: timezone_selector 52#: A function that receives (store, request) and returns a timezone 53#: to be used for a request. If not defined, uses `default_timezone`. 54#: Can also be a string in dotted notation to be imported. 55#: 56#: date_formats 57#: Default date formats for datetime, date and time. 58default_config = { 59 'translations_path': 'locale', 60 'domains': ['messages'], 61 'default_locale': 'en_US', 62 'default_timezone': 'UTC', 63 'locale_selector': None, 64 'timezone_selector': None, 65 'date_formats': { 66 'time': 'medium', 67 'date': 'medium', 68 'datetime': 'medium', 69 'time.short': None, 70 'time.medium': None, 71 'time.full': None, 72 'time.long': None, 73 'time.iso': "HH':'mm':'ss", 74 'date.short': None, 75 'date.medium': None, 76 'date.full': None, 77 'date.long': None, 78 'date.iso': "yyyy'-'MM'-'dd", 79 'datetime.short': None, 80 'datetime.medium': None, 81 'datetime.full': None, 82 'datetime.long': None, 83 'datetime.iso': "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ", 84 }, 85} 86 87NullTranslations = gettext_stdlib.NullTranslations 88 89 90class I18nStore(object): 91 """Internalization store. 92 93 Caches loaded translations and configuration to be used between requests. 94 """ 95 96 #: Configuration key. 97 config_key = __name__ 98 #: A dictionary with all loaded translations. 99 translations = None 100 #: Path to where traslations are stored. 101 translations_path = None 102 #: Translation domains to merge. 103 domains = None 104 #: Default locale code. 105 default_locale = None 106 #: Default timezone code. 107 default_timezone = None 108 #: Dictionary of default date formats. 109 date_formats = None 110 #: A callable that returns the locale for a request. 111 locale_selector = None 112 #: A callable that returns the timezone for a request. 113 timezone_selector = None 114 115 def __init__(self, app, config=None): 116 """Initializes the i18n store. 117 118 :param app: 119 A :class:`webapp2.WSGIApplication` instance. 120 :param config: 121 A dictionary of configuration values to be overridden. See 122 the available keys in :data:`default_config`. 123 """ 124 config = app.config.load_config(self.config_key, 125 default_values=default_config, user_values=config, 126 required_keys=None) 127 self.translations = {} 128 self.translations_path = config['translations_path'] 129 self.domains = config['domains'] 130 self.default_locale = config['default_locale'] 131 self.default_timezone = config['default_timezone'] 132 self.date_formats = config['date_formats'] 133 self.set_locale_selector(config['locale_selector']) 134 self.set_timezone_selector(config['timezone_selector']) 135 136 def set_locale_selector(self, func): 137 """Sets the function that defines the locale for a request. 138 139 :param func: 140 A callable that receives (store, request) and returns the locale 141 for a request. 142 """ 143 if func is None: 144 self.locale_selector = self.default_locale_selector 145 else: 146 if isinstance(func, basestring): 147 func = webapp2.import_string(func) 148 149 # Functions are descriptors, so bind it to this instance with 150 # __get__. 151 self.locale_selector = func.__get__(self, self.__class__) 152 153 def set_timezone_selector(self, func): 154 """Sets the function that defines the timezone for a request. 155 156 :param func: 157 A callable that receives (store, request) and returns the timezone 158 for a request. 159 """ 160 if func is None: 161 self.timezone_selector = self.default_timezone_selector 162 else: 163 if isinstance(func, basestring): 164 func = webapp2.import_string(func) 165 166 self.timezone_selector = func.__get__(self, self.__class__) 167 168 def default_locale_selector(self, request): 169 return self.default_locale 170 171 def default_timezone_selector(self, request): 172 return self.default_timezone 173 174 def get_translations(self, locale): 175 """Returns a translation catalog for a locale. 176 177 :param locale: 178 A locale code. 179 :returns: 180 A ``babel.support.Translations`` instance, or 181 ``gettext.NullTranslations`` if none was found. 182 """ 183 trans = self.translations.get(locale) 184 if not trans: 185 locales = (locale, self.default_locale) 186 trans = self.load_translations(self.translations_path, locales, 187 self.domains) 188 if not webapp2.get_app().debug: 189 self.translations[locale] = trans 190 191 return trans 192 193 def load_translations(self, dirname, locales, domains): 194 """Loads a translation catalog. 195 196 :param dirname: 197 Path to where translations are stored. 198 :param locales: 199 A list of locale codes. 200 :param domains: 201 A list of domains to be merged. 202 :returns: 203 A ``babel.support.Translations`` instance, or 204 ``gettext.NullTranslations`` if none was found. 205 """ 206 trans = None 207 trans_null = None 208 for domain in domains: 209 _trans = support.Translations.load(dirname, locales, domain) 210 if isinstance(_trans, NullTranslations): 211 trans_null = _trans 212 continue 213 elif trans is None: 214 trans = _trans 215 else: 216 trans.merge(_trans) 217 218 return trans or trans_null or NullTranslations() 219 220 221class I18n(object): 222 """Internalization provider for a single request.""" 223 224 #: A reference to :class:`I18nStore`. 225 store = None 226 #: The current locale code. 227 locale = None 228 #: The current translations. 229 translations = None 230 #: The current timezone code. 231 timezone = None 232 #: The current tzinfo object. 233 tzinfo = None 234 235 def __init__(self, request): 236 """Initializes the i18n provider for a request. 237 238 :param request: 239 A :class:`webapp2.Request` instance. 240 """ 241 self.store = store = get_store(app=request.app) 242 self.set_locale(store.locale_selector(request)) 243 self.set_timezone(store.timezone_selector(request)) 244 245 def set_locale(self, locale): 246 """Sets the locale code for this request. 247 248 :param locale: 249 A locale code. 250 """ 251 self.locale = locale 252 self.translations = self.store.get_translations(locale) 253 254 def set_timezone(self, timezone): 255 """Sets the timezone code for this request. 256 257 :param timezone: 258 A timezone code. 259 """ 260 self.timezone = timezone 261 self.tzinfo = pytz.timezone(timezone) 262 263 def gettext(self, string, **variables): 264 """Translates a given string according to the current locale. 265 266 :param string: 267 The string to be translated. 268 :param variables: 269 Variables to format the returned string. 270 :returns: 271 The translated string. 272 """ 273 if variables: 274 return self.translations.ugettext(string) % variables 275 276 return self.translations.ugettext(string) 277 278 def ngettext(self, singular, plural, n, **variables): 279 """Translates a possible pluralized string according to the current 280 locale. 281 282 :param singular: 283 The singular for of the string to be translated. 284 :param plural: 285 The plural for of the string to be translated. 286 :param n: 287 An integer indicating if this is a singular or plural. If greater 288 than 1, it is a plural. 289 :param variables: 290 Variables to format the returned string. 291 :returns: 292 The translated string. 293 """ 294 if variables: 295 return self.translations.ungettext(singular, plural, n) % variables 296 297 return self.translations.ungettext(singular, plural, n) 298 299 def to_local_timezone(self, datetime): 300 """Returns a datetime object converted to the local timezone. 301 302 :param datetime: 303 A ``datetime`` object. 304 :returns: 305 A ``datetime`` object normalized to a timezone. 306 """ 307 if datetime.tzinfo is None: 308 datetime = datetime.replace(tzinfo=pytz.UTC) 309 310 return self.tzinfo.normalize(datetime.astimezone(self.tzinfo)) 311 312 def to_utc(self, datetime): 313 """Returns a datetime object converted to UTC and without tzinfo. 314 315 :param datetime: 316 A ``datetime`` object. 317 :returns: 318 A naive ``datetime`` object (no timezone), converted to UTC. 319 """ 320 if datetime.tzinfo is None: 321 datetime = self.tzinfo.localize(datetime) 322 323 return datetime.astimezone(pytz.UTC).replace(tzinfo=None) 324 325 def _get_format(self, key, format): 326 """A helper for the datetime formatting functions. Returns a format 327 name or pattern to be used by Babel date format functions. 328 329 :param key: 330 A format key to be get from config. Valid values are "date", 331 "datetime" or "time". 332 :param format: 333 The format to be returned. Valid values are "short", "medium", 334 "long", "full" or a custom date/time pattern. 335 :returns: 336 A format name or pattern to be used by Babel date format functions. 337 """ 338 if format is None: 339 format = self.store.date_formats.get(key) 340 341 if format in ('short', 'medium', 'full', 'long', 'iso'): 342 rv = self.store.date_formats.get('%s.%s' % (key, format)) 343 if rv is not None: 344 format = rv 345 346 return format 347 348 def format_date(self, date=None, format=None, rebase=True): 349 """Returns a date formatted according to the given pattern and 350 following the current locale. 351 352 :param date: 353 A ``date`` or ``datetime`` object. If None, the current date in 354 UTC is used. 355 :param format: 356 The format to be returned. Valid values are "short", "medium", 357 "long", "full" or a custom date/time pattern. Example outputs: 358 359 - short: 11/10/09 360 - medium: Nov 10, 2009 361 - long: November 10, 2009 362 - full: Tuesday, November 10, 2009 363 364 :param rebase: 365 If True, converts the date to the current :attr:`timezone`. 366 :returns: 367 A formatted date in unicode. 368 """ 369 format = self._get_format('date', format) 370 371 if rebase and isinstance(date, datetime.datetime): 372 date = self.to_local_timezone(date) 373 374 return dates.format_date(date, format, locale=self.locale) 375 376 def format_datetime(self, datetime=None, format=None, rebase=True): 377 """Returns a date and time formatted according to the given pattern 378 and following the current locale and timezone. 379 380 :param datetime: 381 A ``datetime`` object. If None, the current date and time in UTC 382 is used. 383 :param format: 384 The format to be returned. Valid values are "short", "medium", 385 "long", "full" or a custom date/time pattern. Example outputs: 386 387 - short: 11/10/09 4:36 PM 388 - medium: Nov 10, 2009 4:36:05 PM 389 - long: November 10, 2009 4:36:05 PM +0000 390 - full: Tuesday, November 10, 2009 4:36:05 PM World (GMT) Time 391 392 :param rebase: 393 If True, converts the datetime to the current :attr:`timezone`. 394 :returns: 395 A formatted date and time in unicode. 396 """ 397 format = self._get_format('datetime', format) 398 399 kwargs = {} 400 if rebase: 401 kwargs['tzinfo'] = self.tzinfo 402 403 return dates.format_datetime(datetime, format, locale=self.locale, 404 **kwargs) 405 406 def format_time(self, time=None, format=None, rebase=True): 407 """Returns a time formatted according to the given pattern and 408 following the current locale and timezone. 409 410 :param time: 411 A ``time`` or ``datetime`` object. If None, the current 412 time in UTC is used. 413 :param format: 414 The format to be returned. Valid values are "short", "medium", 415 "long", "full" or a custom date/time pattern. Example outputs: 416 417 - short: 4:36 PM 418 - medium: 4:36:05 PM 419 - long: 4:36:05 PM +0000 420 - full: 4:36:05 PM World (GMT) Time 421 422 :param rebase: 423 If True, converts the time to the current :attr:`timezone`. 424 :returns: 425 A formatted time in unicode. 426 """ 427 format = self._get_format('time', format) 428 429 kwargs = {} 430 if rebase: 431 kwargs['tzinfo'] = self.tzinfo 432 433 return dates.format_time(time, format, locale=self.locale, **kwargs) 434 435 def format_timedelta(self, datetime_or_timedelta, granularity='second', 436 threshold=.85): 437 """Formats the elapsed time from the given date to now or the given 438 timedelta. This currently requires an unreleased development version 439 of Babel. 440 441 :param datetime_or_timedelta: 442 A ``timedelta`` object representing the time difference to format, 443 or a ``datetime`` object in UTC. 444 :param granularity: 445 Determines the smallest unit that should be displayed, the value 446 can be one of "year", "month", "week", "day", "hour", "minute" or 447 "second". 448 :param threshold: 449 Factor that determines at which point the presentation switches to 450 the next higher unit. 451 :returns: 452 A string with the elapsed time. 453 """ 454 if isinstance(datetime_or_timedelta, datetime.datetime): 455 datetime_or_timedelta = datetime.datetime.utcnow() - \ 456 datetime_or_timedelta 457 458 return dates.format_timedelta(datetime_or_timedelta, granularity, 459 threshold=threshold, 460 locale=self.locale) 461 462 def format_number(self, number): 463 """Returns the given number formatted for the current locale. Example:: 464 465 >>> format_number(1099, locale='en_US') 466 u'1,099' 467 468 :param number: 469 The number to format. 470 :returns: 471 The formatted number. 472 """ 473 return numbers.format_number(number, locale=self.locale) 474 475 def format_decimal(self, number, format=None): 476 """Returns the given decimal number formatted for the current locale. 477 Example:: 478 479 >>> format_decimal(1.2345, locale='en_US') 480 u'1.234' 481 >>> format_decimal(1.2346, locale='en_US') 482 u'1.235' 483 >>> format_decimal(-1.2346, locale='en_US') 484 u'-1.235' 485 >>> format_decimal(1.2345, locale='sv_SE') 486 u'1,234' 487 >>> format_decimal(12345, locale='de') 488 u'12.345' 489 490 The appropriate thousands grouping and the decimal separator are used 491 for each locale:: 492 493 >>> format_decimal(12345.5, locale='en_US') 494 u'12,345.5' 495 496 :param number: 497 The number to format. 498 :param format: 499 Notation format. 500 :returns: 501 The formatted decimal number. 502 """ 503 return numbers.format_decimal(number, format=format, 504 locale=self.locale) 505 506 def format_currency(self, number, currency, format=None): 507 """Returns a formatted currency value. Example:: 508 509 >>> format_currency(1099.98, 'USD', locale='en_US') 510 u'$1,099.98' 511 >>> format_currency(1099.98, 'USD', locale='es_CO') 512 u'US$\\xa01.099,98' 513 >>> format_currency(1099.98, 'EUR', locale='de_DE') 514 u'1.099,98\\xa0\\u20ac' 515 516 The pattern can also be specified explicitly:: 517 518 >>> format_currency(1099.98, 'EUR', u'\\xa4\\xa4 #,##0.00', 519 ... locale='en_US') 520 u'EUR 1,099.98' 521 522 :param number: 523 The number to format. 524 :param currency: 525 The currency code. 526 :param format: 527 Notation format. 528 :returns: 529 The formatted currency value. 530 """ 531 return numbers.format_currency(number, currency, format=format, 532 locale=self.locale) 533 534 def format_percent(self, number, format=None): 535 """Returns formatted percent value for the current locale. Example:: 536 537 >>> format_percent(0.34, locale='en_US') 538 u'34%' 539 >>> format_percent(25.1234, locale='en_US') 540 u'2,512%' 541 >>> format_percent(25.1234, locale='sv_SE') 542 u'2\\xa0512\\xa0%' 543 544 The format pattern can also be specified explicitly:: 545 546 >>> format_percent(25.1234, u'#,##0\u2030', locale='en_US') 547 u'25,123\u2030' 548 549 :param number: 550 The percent number to format 551 :param format: 552 Notation format. 553 :returns: 554 The formatted percent number. 555 """ 556 return numbers.format_percent(number, format=format, 557 locale=self.locale) 558 559 def format_scientific(self, number, format=None): 560 """Returns value formatted in scientific notation for the current 561 locale. Example:: 562 563 >>> format_scientific(10000, locale='en_US') 564 u'1E4' 565 566 The format pattern can also be specified explicitly:: 567 568 >>> format_scientific(1234567, u'##0E00', locale='en_US') 569 u'1.23E06' 570 571 :param number: 572 The number to format. 573 :param format: 574 Notation format. 575 :returns: 576 Value formatted in scientific notation. 577 """ 578 return numbers.format_scientific(number, format=format, 579 locale=self.locale) 580 581 def parse_date(self, string): 582 """Parses a date from a string. 583 584 This function uses the date format for the locale as a hint to 585 determine the order in which the date fields appear in the string. 586 Example:: 587 588 >>> parse_date('4/1/04', locale='en_US') 589 datetime.date(2004, 4, 1) 590 >>> parse_date('01.04.2004', locale='de_DE') 591 datetime.date(2004, 4, 1) 592 593 :param string: 594 The string containing the date. 595 :returns: 596 The parsed date object. 597 """ 598 return dates.parse_date(string, locale=self.locale) 599 600 def parse_datetime(self, string): 601 """Parses a date and time from a string. 602 603 This function uses the date and time formats for the locale as a hint 604 to determine the order in which the time fields appear in the string. 605 606 :param string: 607 The string containing the date and time. 608 :returns: 609 The parsed datetime object. 610 """ 611 return dates.parse_datetime(string, locale=self.locale) 612 613 def parse_time(self, string): 614 """Parses a time from a string. 615 616 This function uses the time format for the locale as a hint to 617 determine the order in which the time fields appear in the string. 618 Example:: 619 620 >>> parse_time('15:30:00', locale='en_US') 621 datetime.time(15, 30) 622 623 :param string: 624 The string containing the time. 625 :returns: 626 The parsed time object. 627 """ 628 return dates.parse_time(string, locale=self.locale) 629 630 def parse_number(self, string): 631 """Parses localized number string into a long integer. Example:: 632 633 >>> parse_number('1,099', locale='en_US') 634 1099L 635 >>> parse_number('1.099', locale='de_DE') 636 1099L 637 638 When the given string cannot be parsed, an exception is raised:: 639 640 >>> parse_number('1.099,98', locale='de') 641 Traceback (most recent call last): 642 ... 643 NumberFormatError: '1.099,98' is not a valid number 644 645 :param string: 646 The string to parse. 647 :returns: 648 The parsed number. 649 :raises: 650 ``NumberFormatError`` if the string can not be converted to a 651 number. 652 """ 653 return numbers.parse_number(string, locale=self.locale) 654 655 def parse_decimal(self, string): 656 """Parses localized decimal string into a float. Example:: 657 658 >>> parse_decimal('1,099.98', locale='en_US') 659 1099.98 660 >>> parse_decimal('1.099,98', locale='de') 661 1099.98 662 663 When the given string cannot be parsed, an exception is raised:: 664 665 >>> parse_decimal('2,109,998', locale='de') 666 Traceback (most recent call last): 667 ... 668 NumberFormatError: '2,109,998' is not a valid decimal number 669 670 :param string: 671 The string to parse. 672 :returns: 673 The parsed decimal number. 674 :raises: 675 ``NumberFormatError`` if the string can not be converted to a 676 decimal number. 677 """ 678 return numbers.parse_decimal(string, locale=self.locale) 679 680 def get_timezone_location(self, dt_or_tzinfo): 681 """Returns a representation of the given timezone using "location 682 format". 683 684 The result depends on both the local display name of the country and 685 the city assocaited with the time zone:: 686 687 >>> from pytz import timezone 688 >>> tz = timezone('America/St_Johns') 689 >>> get_timezone_location(tz, locale='de_DE') 690 u"Kanada (St. John's)" 691 >>> tz = timezone('America/Mexico_City') 692 >>> get_timezone_location(tz, locale='de_DE') 693 u'Mexiko (Mexiko-Stadt)' 694 695 If the timezone is associated with a country that uses only a single 696 timezone, just the localized country name is returned:: 697 698 >>> tz = timezone('Europe/Berlin') 699 >>> get_timezone_name(tz, locale='de_DE') 700 u'Deutschland' 701 702 :param dt_or_tzinfo: 703 The ``datetime`` or ``tzinfo`` object that determines 704 the timezone; if None, the current date and time in UTC is assumed. 705 :returns: 706 The localized timezone name using location format. 707 """ 708 return dates.get_timezone_name(dt_or_tzinfo, locale=self.locale) 709 710 711def gettext(string, **variables): 712 """See :meth:`I18n.gettext`.""" 713 return get_i18n().gettext(string, **variables) 714 715 716def ngettext(singular, plural, n, **variables): 717 """See :meth:`I18n.ngettext`.""" 718 return get_i18n().ngettext(singular, plural, n, **variables) 719 720 721def to_local_timezone(datetime): 722 """See :meth:`I18n.to_local_timezone`.""" 723 return get_i18n().to_local_timezone(datetime) 724 725 726def to_utc(datetime): 727 """See :meth:`I18n.to_utc`.""" 728 return get_i18n().to_utc(datetime) 729 730 731def format_date(date=None, format=None, rebase=True): 732 """See :meth:`I18n.format_date`.""" 733 return get_i18n().format_date(date, format, rebase) 734 735 736def format_datetime(datetime=None, format=None, rebase=True): 737 """See :meth:`I18n.format_datetime`.""" 738 return get_i18n().format_datetime(datetime, format, rebase) 739 740 741def format_time(time=None, format=None, rebase=True): 742 """See :meth:`I18n.format_time`.""" 743 return get_i18n().format_time(time, format, rebase) 744 745 746def format_timedelta(datetime_or_timedelta, granularity='second', 747 threshold=.85): 748 """See :meth:`I18n.format_timedelta`.""" 749 return get_i18n().format_timedelta(datetime_or_timedelta, 750 granularity, threshold) 751 752 753def format_number(number): 754 """See :meth:`I18n.format_number`.""" 755 return get_i18n().format_number(number) 756 757 758def format_decimal(number, format=None): 759 """See :meth:`I18n.format_decimal`.""" 760 return get_i18n().format_decimal(number, format) 761 762 763def format_currency(number, currency, format=None): 764 """See :meth:`I18n.format_currency`.""" 765 return get_i18n().format_currency(number, currency, format) 766 767 768def format_percent(number, format=None): 769 """See :meth:`I18n.format_percent`.""" 770 return get_i18n().format_percent(number, format) 771 772 773def format_scientific(number, format=None): 774 """See :meth:`I18n.format_scientific`.""" 775 return get_i18n().format_scientific(number, format) 776 777 778def parse_date(string): 779 """See :meth:`I18n.parse_date`""" 780 return get_i18n().parse_date(string) 781 782 783def parse_datetime(string): 784 """See :meth:`I18n.parse_datetime`.""" 785 return get_i18n().parse_datetime(string) 786 787 788def parse_time(string): 789 """See :meth:`I18n.parse_time`.""" 790 return get_i18n().parse_time(string) 791 792 793def parse_number(string): 794 """See :meth:`I18n.parse_number`.""" 795 return get_i18n().parse_number(string) 796 797 798def parse_decimal(string): 799 """See :meth:`I18n.parse_decimal`.""" 800 return get_i18n().parse_decimal(string) 801 802 803def get_timezone_location(dt_or_tzinfo): 804 """See :meth:`I18n.get_timezone_location`.""" 805 return get_i18n().get_timezone_location(dt_or_tzinfo) 806 807 808def lazy_gettext(string, **variables): 809 """A lazy version of :func:`gettext`. 810 811 :param string: 812 The string to be translated. 813 :param variables: 814 Variables to format the returned string. 815 :returns: 816 A ``babel.support.LazyProxy`` object that when accessed translates 817 the string. 818 """ 819 return support.LazyProxy(gettext, string, **variables) 820 821 822# Aliases. 823_ = gettext 824_lazy = lazy_gettext 825 826 827# Factories ------------------------------------------------------------------- 828 829 830#: Key used to store :class:`I18nStore` in the app registry. 831_store_registry_key = 'webapp2_extras.i18n.I18nStore' 832#: Key used to store :class:`I18n` in the request registry. 833_i18n_registry_key = 'webapp2_extras.i18n.I18n' 834 835 836def get_store(factory=I18nStore, key=_store_registry_key, app=None): 837 """Returns an instance of :class:`I18nStore` from the app registry. 838 839 It'll try to get it from the current app registry, and if it is not 840 registered it'll be instantiated and registered. A second call to this 841 function will return the same instance. 842 843 :param factory: 844 The callable used to build and register the instance if it is not yet 845 registered. The default is the class :class:`I18nStore` itself. 846 :param key: 847 The key used to store the instance in the registry. A default is used 848 if it is not set. 849 :param app: 850 A :class:`webapp2.WSGIApplication` instance used to store the instance. 851 The active app is used if it is not set. 852 """ 853 app = app or webapp2.get_app() 854 store = app.registry.get(key) 855 if not store: 856 store = app.registry[key] = factory(app) 857 858 return store 859 860 861def set_store(store, key=_store_registry_key, app=None): 862 """Sets an instance of :class:`I18nStore` in the app registry. 863 864 :param store: 865 An instance of :class:`I18nStore`. 866 :param key: 867 The key used to retrieve the instance from the registry. A default 868 is used if it is not set. 869 :param request: 870 A :class:`webapp2.WSGIApplication` instance used to retrieve the 871 instance. The active app is used if it is not set. 872 """ 873 app = app or webapp2.get_app() 874 app.registry[key] = store 875 876 877def get_i18n(factory=I18n, key=_i18n_registry_key, request=None): 878 """Returns an instance of :class:`I18n` from the request registry. 879 880 It'll try to get it from the current request registry, and if it is not 881 registered it'll be instantiated and registered. A second call to this 882 function will return the same instance. 883 884 :param factory: 885 The callable used to build and register the instance if it is not yet 886 registered. The default is the class :class:`I18n` itself. 887 :param key: 888 The key used to store the instance in the registry. A default is used 889 if it is not set. 890 :param request: 891 A :class:`webapp2.Request` instance used to store the instance. The 892 active request is used if it is not set. 893 """ 894 request = request or webapp2.get_request() 895 i18n = request.registry.get(key) 896 if not i18n: 897 i18n = request.registry[key] = factory(request) 898 899 return i18n 900 901 902def set_i18n(i18n, key=_i18n_registry_key, request=None): 903 """Sets an instance of :class:`I18n` in the request registry. 904 905 :param store: 906 An instance of :class:`I18n`. 907 :param key: 908 The key used to retrieve the instance from the registry. A default 909 is used if it is not set. 910 :param request: 911 A :class:`webapp2.Request` instance used to retrieve the instance. The 912 active request is used if it is not set. 913 """ 914 request = request or webapp2.get_request() 915 request.registry[key] = i18n 916