flufl.i18n-2.0.1/0000775000076500000240000000000013202606453013645 5ustar barrystaff00000000000000flufl.i18n-2.0.1/coverage.ini0000664000076500000240000000034313135507413016142 0ustar barrystaff00000000000000[run] branch = true parallel = true omit = setup* flufl/i18n/testing/* flufl/i18n/tests/* .tox/*/lib/python3.*/site-packages/* [paths] source = flufl/i18n .tox/*/lib/python*/site-packages/flufl/i18n flufl.i18n-2.0.1/flufl/0000775000076500000240000000000013202606453014755 5ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl/__init__.py0000664000076500000240000000007013135507413017064 0ustar barrystaff00000000000000__import__('pkg_resources').declare_namespace(__name__) flufl.i18n-2.0.1/flufl/i18n/0000775000076500000240000000000013202606453015534 5ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl/i18n/__init__.py0000664000076500000240000000121113202602257017636 0ustar barrystaff00000000000000"""Expose sub-module names in the package namespace.""" from flufl.i18n._application import Application # noqa: F401 from flufl.i18n._expand import expand # noqa: F401 from flufl.i18n._registry import registry from flufl.i18n._strategy import * # noqa: F403 from public import public __version__ = '2.0.1' @public def initialize(domain): """A convenience function for setting up translation. :param domain: The application's name. :type domain: string """ strategy = SimpleStrategy(domain) # noqa: F405 application = registry.register(strategy) return application._ flufl.i18n-2.0.1/flufl/i18n/_application.py0000664000076500000240000002101513135517410020546 0ustar barrystaff00000000000000"""An application.""" from flufl.i18n._translator import Translator from gettext import NullTranslations from public import public class _Using: """Context manager for _.using().""" def __init__(self, application, language_code): self._application = application self._language_code = language_code def __enter__(self): self._application.push(self._language_code) def __exit__(self, *exc_info): self._application.pop() # Do not suppress exceptions. return False class _Defer: """Context manager for _.defer_translation().""" def __init__(self, application): self._application = application def __enter__(self): self._application.defer() def __exit__(self, *exc_info): self._application.pop() # Do not suppress exceptions. return False class _Underscore: """The implementation of the _() function. This class is internal representation only and has an incestuous relationship with the Application class. """ def __init__(self, application): self._application = application def __call__(self, original, extras=None): """Translate the string into the language of the current context. :param original: The original string to translate. :type original: string :param extras: Extra substitution mapping, elements of which override the locals and globals. :return: The translated string. :rtype: string """ return self._application.current.translate(original, extras) def using(self, language_code): """Create a context manager for temporary translation. While in this context manager, translations use the given language code. When the with statement exits, the original language is restored. These are nestable. """ return _Using(self._application, language_code) def defer_translation(self): """Push a NullTranslations onto the stack. This is useful for when you want to mark strings statically for extraction but you want to defer translation of the string until later. """ return _Defer(self._application) def push(self, language_code): """Push a new catalog onto the stack. The translation catalog associated with the language code now becomes the currently active translation context. """ self._application.push(language_code) def pop(self): """Pop the current catalog off the translation stack. No exception is raised for under-runs. In that case, pop() just no-ops and the null translation becomes the current translation context. """ self._application.pop() @property def default(self): """Return the default language code. :return: The default language code. :rtype: string or None if there is no default language """ return self._application.default @default.setter def default(self, language_code): """Set the default language code. :param language_code: The language code for the default translator. :type language_code: string """ self._application.default = language_code @default.deleter def default(self): """Reset the default language to the null translator.""" del self._application.default @property def code(self): """Return the language code currently in effect.""" code = self._application.code if code is None: return self.default return code @public class Application: """Manage all the catalogs for a particular application. You can ask the application for a specific catalog based on the language code. The Application requires a strategy for finding catalog files. Attributes: * dedent (default True) - controls whether translated strings are dedented or not. This is passed through to the underlying `Translator` instance. * depth (default 2) - The number of stack frames to call sys._getframe() with in the underlying `Translator` instance. Passed through to that class's constructor. """ def __init__(self, strategy): """Create an `Application`. Use the `dedent` attribute on this instance to control whether translated strings are dedented or not. This is passed straight through to the `Translator` instance created in the _() method. :param strategy: A callable that can find catalog files for the application based on the language code. :type strategy: callable taking one string argument, the language code. """ self._strategy = strategy # A mapping from language codes to catalogs. self._catalogs = {} self._stack = [] # Arguments to the Translator constructor. self.dedent = True self.depth = 2 # By default, the baseline translator is the null translator. Use our # public API so that we share code. self._default_language = None self._default_translator = None # This sets the _default_translator. del self.default @property def name(self): """The application name. :return: The application name. :rtype: string """ return self._strategy.name @property def default(self): """Return the default language code. :return: The default language code. :rtype: string or None if there is no default language """ return self._default_language @default.setter def default(self, language_code): """Set the default language code. :param language_code: The language code for the default translator. :type language_code: string """ self._default_language = language_code catalog = self.get(language_code) self._default_translator = Translator(catalog, self.dedent, self.depth) @default.deleter def default(self): """Reset the default language to the null translator.""" self._default_language = None self._default_translator = Translator( self._strategy(), self.dedent, self.depth) def get(self, language_code): """Get the catalog associated with the language code. :param language_code: The language code. :type language_code: string :return: A `gettext` catalog. :rtype: `gettext.NullTranslations` or subclass. """ missing = object() catalog = self._catalogs.get(language_code, missing) if catalog is missing: catalog = self._strategy(language_code) self._catalogs[language_code] = catalog return catalog @property def _(self): """Return a translator object, tied to the current catalog. :return: A translator context object for the current active catalog. :rtype: `Translator` """ return _Underscore(self) def defer(self): """Push a deferred (i.e. null) translation context onto the stack. This is primarily used to support the ``_.defer_translation()`` context manager. """ self._stack.append( ('', Translator(NullTranslations(), self.dedent, self.depth))) def push(self, language_code): """Push a new catalog onto the stack. The translation catalog associated with the language code now becomes the currently active translation context. """ catalog = self.get(language_code) translator = Translator(catalog, self.dedent, self.depth) self._stack.append((language_code, translator)) def pop(self): """Pop the current catalog off the translation stack. No exception is raised for under-runs. In that case, pop() just no-ops and the null translation becomes the current translation context. """ if len(self._stack) > 0: self._stack.pop() @property def current(self): """Return the current translator. :return: The current translator. :rtype: `Translator` """ if len(self._stack) == 0: return self._default_translator return self._stack[-1][1] @property def code(self): """Return the current language code. :return: The current language code. :rtype: string """ if len(self._stack) == 0: return None return self._stack[-1][0] flufl.i18n-2.0.1/flufl/i18n/_expand.py0000664000076500000240000000142713135507413017531 0ustar barrystaff00000000000000"""String interpolation.""" import logging from public import public from string import Template log = logging.getLogger('flufl.i18n') @public def expand(template, substitutions, template_class=Template): """Expand string template with substitutions. :param template: A PEP 292 $-string template. :type template: string :param substitutions: The substitutions dictionary. :type substitutions: dict :param template_class: The template class to use. :type template_class: class :return: The substituted string. :rtype: string """ try: return template_class(template).safe_substitute(substitutions) except (TypeError, ValueError): # The template is really screwed up. log.exception('broken template: %s', template) flufl.i18n-2.0.1/flufl/i18n/_registry.py0000664000076500000240000000155213135507413020121 0ustar barrystaff00000000000000"""Translation registry.""" from flufl.i18n._application import Application from public import public @public class Registry: """A registry of application translation lookup strategies.""" def __init__(self): # Map application names to Application instances. self._registry = {} def register(self, strategy): """Add an association between an application and a lookup strategy. :param strategy: An application translation lookup strategy. :type application: A callable object with a .name attribute :return: An application instance which can be used to access the language catalogs for the application. :rtype: `Application` """ application = Application(strategy) self._registry[strategy.name] = application return application public(registry=Registry()) flufl.i18n-2.0.1/flufl/i18n/_strategy.py0000664000076500000240000000436313135507413020116 0ustar barrystaff00000000000000"""Catalog search strategies.""" import os import gettext from public import public class _BaseStrategy: """Common code for strategies.""" def __init__(self, name): """Create a catalog lookup strategy. :param name: The application's name. :type name: string """ self.name = name self._messages_dir = None def __call__(self, language_code=None): """Find the catalog for the language. :param language_code: The language code to find. If None, then the default gettext language code lookup scheme is used. :type language_code: string :return: A `gettext` catalog. :rtype: `gettext.NullTranslations` or subclass """ # gettext.translation() requires None or a sequence. languages = (None if language_code is None else [language_code]) try: return gettext.translation( self.name, self._messages_dir, languages) except IOError: # Fall back to untranslated source language. return gettext.NullTranslations() @public class PackageStrategy(_BaseStrategy): """A strategy that finds catalogs based on package paths.""" def __init__(self, name, package): """Create a catalog lookup strategy. :param name: The application's name. :type name: string :param package: The package path to the message catalogs. This strategy uses the __file__ of the package path as the directory containing `gettext` messages. :type package_name: module """ super().__init__(name) self._messages_dir = os.path.dirname(package.__file__) @public class SimpleStrategy(_BaseStrategy): """A simpler strategy for getting translations.""" def __init__(self, name): """Create a catalog lookup strategy. :param name: The application's name. :type name: string :param package: The package path to the message catalogs. This strategy uses the __file__ of the package path as the directory containing `gettext` messages. :type package_name: module """ super().__init__(name) self._messages_dir = os.environ.get('LOCPATH') flufl.i18n-2.0.1/flufl/i18n/_substitute.py0000664000076500000240000000104713135507413020463 0ustar barrystaff00000000000000"""Substitutions.""" import string from public import public _missing = object() @public class Template(string.Template): """Match any attribute path.""" idpattern = r'[_a-z][_a-z0-9.]*' @public class attrdict(dict): """Follow attribute paths.""" def __getitem__(self, key): parts = key.split('.') value = super().__getitem__(parts.pop(0)) while parts: value = getattr(value, parts.pop(0), _missing) if value is _missing: raise KeyError(key) return value flufl.i18n-2.0.1/flufl/i18n/_translator.py0000664000076500000240000000550613135507413020445 0ustar barrystaff00000000000000"""Basic translation context class.""" import sys import textwrap from flufl.i18n._expand import expand from flufl.i18n._substitute import Template, attrdict from public import public @public class Translator: """A translation context.""" def __init__(self, catalog, dedent=True, depth=2): """Create a translation context. :param catalog: The translation catalog. :type catalog: `gettext.NullTranslations` or subclass :param dedent: Whether the input string should be dedented. :type dedent: bool :param depth: Number of stack frames to call sys._getframe() with. :type depth: int """ self._catalog = catalog self.dedent = dedent self.depth = depth def translate(self, original, extras=None): """Translate the string. :param original: The original string to translate. :type original: string :param extras: Extra substitution mapping, elements of which override the locals and globals. :return: The translated string. :rtype: string """ if original == '': return '' assert original, 'Cannot translate: {}'.format(original) # Because the original string is what the text extractors put into the # catalog, we must first look up the original unadulterated string in # the catalog. Use the global translation context for this. # # Translations must be unicode safe internally. The translation # service is one boundary to the outside world, so to honor this # constraint, make sure that all strings to come out of this are # unicodes, even if the translated string or dictionary values are # 8-bit strings. tns = self._catalog.gettext(original) # Do PEP 292 style $-string interpolation into the resulting string. # # This lets you write something like: # # now = time.ctime(time.time()) # print _('The current time is: $now') # # and have it Just Work. Key precedence is: # # extras > locals > globals # # Get the frame of the caller. frame = sys._getframe(self.depth) # Create the raw dictionary of substitutions. raw_dict = frame.f_globals.copy() raw_dict.update(frame.f_locals) if extras is not None: raw_dict.update(extras) # Python 2 requires ** dictionaries to have str, not unicode keys. # For our purposes, keys should always be ascii. Values though should # be unicode. translated_string = expand(tns, attrdict(raw_dict), Template) # Dedent the string if so desired. if self.dedent: translated_string = textwrap.dedent(translated_string) return translated_string flufl.i18n-2.0.1/flufl/i18n/conf.py0000664000076500000240000001532613135507413017043 0ustar barrystaff00000000000000# -*- coding: utf-8 -*- # # flufl.i18n documentation build configuration file, created by # sphinx-quickstart on Fri Apr 23 11:41:37 2010. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. from __future__ import print_function import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.append(os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc'] # Add any paths that contain templates here, relative to this directory. templates_path = ['../../_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8' # The master toctree document. master_doc = 'README' # General information about the project. project = 'flufl.i18n' copyright = '2009-2017, Barry Warsaw' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # from flufl.i18n import __version__ # The short X.Y version. version = __version__ # The full version, including alpha/beta/rc tags. release = __version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of documents that shouldn't be included in the build. #unused_docs = [] # List of directories, relative to source directory, that shouldn't be searched # for source files. exclude_trees = ['_build', 'build', 'flufl.i18n.egg-info', 'distribute-0.6.10'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. Major themes that come with # Sphinx are currently 'default' and 'sphinxdoc'. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['../../_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_use_modindex = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = '' # Output file base name for HTML help builder. htmlhelp_basename = 'flufli18ndoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('README.rst', 'flufli18n.tex', 'flufl.i18n Documentation', 'Barry A. Warsaw', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_use_modindex = True import errno def index_html(): cwd = os.getcwd() try: os.chdir('build/sphinx/html') try: os.symlink('README.html', 'index.html') except OSError as error: if error.errno != errno.EEXIST: raise print('index.html -> README.html') finally: os.chdir(cwd) import atexit atexit.register(index_html) flufl.i18n-2.0.1/flufl/i18n/docs/0000775000076500000240000000000013202606453016464 5ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl/i18n/docs/__init__.py0000664000076500000240000000000013135507413020564 0ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl/i18n/docs/expand.rst0000664000076500000240000000126613202606366020505 0ustar barrystaff00000000000000=================== Expanding templates =================== `PEP 292`_ defines a simplified string template, where substitution variables are identified by a leading ``$``-sign. The substitution dictionary names the keys and values that should be interpolated into the template:: >>> key_1 = 'key_1' >>> key_2 = 'key_2' >>> from flufl.i18n import expand >>> # This may fail for Python < 2.6.5 >>> print(expand( ... '$key_2 is different than $key_1', { ... key_1: 'the first key', ... key_2: 'the second key', ... })) the second key is different than the first key .. _`PEP 292`: http://www.python.org/dev/peps/pep-0292/ flufl.i18n-2.0.1/flufl/i18n/docs/strategies.rst0000664000076500000240000000563113202606366021400 0ustar barrystaff00000000000000================== Catalog strategies ================== The way ``flufl.i18n`` finds its catalog for an application is extensible. These are called *strategies*. ``flufl.i18n`` comes with a couple of fairly simple strategies. The first locates catalog files from within a package's directory. Inside the package directory, you still need the ``gettext`` standard layout of ``/LC_MESSAGES/.mo``. Python package strategies ========================= For example, to use the catalog in ``flufl.i18n``'s testing package, you would use the ``PackageStrategy``. >>> from flufl.i18n import PackageStrategy >>> import flufl.i18n.testing.messages By setting the ``$LANG`` environment variable, we can specify that the application translates into that language automatically. >>> # The testing 'xx' language rot13's the source string. >>> import os >>> os.environ['LANG'] = 'xx' The first argument is the application name, which must be unique among all registered strategies. The second argument is the package in which to search. >>> strategy = PackageStrategy('flufl', flufl.i18n.testing.messages) Once you have the desired strategy, register this with the global registry. The registration process returns an application object which can be used to look up language codes. >>> from flufl.i18n import registry >>> application = registry.register(strategy) The application object keeps track of a current translation catalog, and exports a method which you can bind to the 'underscore' function in your module globals for convenient gettext usage. >>> _ = application._ By doing so, at run time, ``_()`` will always translate the string argument to the current catalog's language. >>> print(_('A test message')) N grfg zrffntr .. >>> # Hack to unregister the previous strategy. >>> registry._registry.clear() Simple strategy =============== There is also a simpler strategy that uses both the ``$LANG`` environment variable, and the ``$LOCPATH`` environment variable to set things up:: >>> os.environ['LOCPATH'] = os.path.dirname( ... flufl.i18n.testing.messages.__file__) >>> from flufl.i18n import SimpleStrategy >>> strategy = SimpleStrategy('flufl') >>> application = registry.register(strategy) >>> _ = application._ >>> print(_('A test message')) N grfg zrffntr Calling with zero arguments =========================== Strategies should be prepared to accept zero arguments when called, to produce a *default* translation (usually the ``gettext.NullTranslator``). Here, we look for the ``gettext()``:: >>> def get_ugettext(strategy): ... catalog = strategy() ... return catalog.gettext >>> print(get_ugettext(SimpleStrategy('example'))('A test message')) A test message >>> print(get_ugettext(PackageStrategy( ... 'example', flufl.i18n.testing.messages))('A test message')) A test message flufl.i18n-2.0.1/flufl/i18n/docs/using.rst0000664000076500000240000001773313202606366020361 0ustar barrystaff00000000000000============================ Using the flufl.i18n library ============================ Set up ====== There are two basic ways that your application can set up translations using this library. The simple initialization will work for most applications, where there is only one language context for the entire run of the application. The more complex initialization works well for applications like servers that may want to use multiple language contexts during their execution. Single language contexts ------------------------ If your application only needs one language context for its entire execution, you can use the simple API to set things up. >>> from flufl.i18n import initialize The library by default uses the ``$LANG`` and ``$LOCPATH`` environment variables to set things up:: >>> # The testing 'xx' language rot13's the source string. The >>> # gettext catalogs are in this package directory. >>> import os >>> import flufl.i18n.testing.messages >>> os.environ['LANG'] = 'xx' >>> os.environ['LOCPATH'] = os.path.dirname( ... flufl.i18n.testing.messages.__file__) Now you just need to call the ``initialize()`` function with the application's name and you'll get an object back that you can assign to the ``_()`` function for run-time translations. >>> _ = initialize('flufl') >>> print(_('A test message')) N grfg zrffntr It's probably best to just share this function through imports, but it does no harm to call ``initialize()`` again. >>> _ = initialize('flufl') >>> print(_('A test message')) N grfg zrffntr .. >>> # Unregister the application domain used earlier. Also, clear the >>> # environment settings from above. >>> from flufl.i18n import registry >>> registry._registry.clear() >>> del os.environ['LANG'] >>> del os.environ['LOCPATH'] Multiple language contexts -------------------------- Some applications, such as servers, are more complex; they need multiple language contexts during their execution. To support this, there is a global registry of catalog look up :doc:`strategies `. When a particular language code is specified, the strategy is used to find the catalog that provides that language's translations. ``flufl.i18n`` comes with a couple of fairly simple strategies, but you can of course write your own. A convenient built-in strategy looks up catalogs from within the package directory using `GNU gettext`_'s convention, where the base directory for the catalogs is rooted in a subpackage. >>> from flufl.i18n import PackageStrategy >>> strategy = PackageStrategy('flufl', flufl.i18n.testing.messages) The first argument is the application name, which must be unique among all registered strategies. The second argument is the package where the translations can be found. Once you have the desired strategy, register this with the global registry. The registration process returns an application object which can be used to look up language codes. >>> from flufl.i18n import registry >>> application = registry.register(strategy) The application object keeps track of a current translation catalog, and exports a method which you can bind to the *underscore* function in your module globals for convenient gettext usage. By doing so, at run time, ``_()`` will always translate the string argument to the current catalog's language. >>> _ = application._ By default the application just translates the source string back into the source string. I.e. it is a null translator. >>> print(_('A test message')) A test message And it has no language code. >>> print(_.code) None You can temporarily push a new language context to the top of the stack, which automatically rebinds the underscore function to the language's catalog. >>> _.push('xx') >>> print(_.code) xx >>> print(_('A test message')) N grfg zrffntr Pop the current language to return to the default. Once you're at the bottom of the stack, more pops will just give you the default translation. >>> _.pop() >>> print(_.code) None >>> print(_('A test message')) A test message >>> _.pop() >>> print(_.code) None >>> print(_('A test message')) A test message The underscore method has a context manager called ``using`` which can be used to temporarily set a new language inside a ``with`` statement:: >>> with _.using('xx'): ... print(_('A test message')) N grfg zrffntr >>> print(_('A test message')) A test message These ``with`` statements are nestable:: >>> with _.using('xx'): ... print(_('A test message')) ... with _.using('yy'): ... print(_('A test message')) ... print(_('A test message')) N grfg zrffntr egassem tset A N grfg zrffntr >>> print(_('A test message')) A test message You can set the bottom language context, which replaces the default null translation:: >>> _.default = 'xx' >>> print(_('A test message')) N grfg zrffntr >>> _.pop() >>> print(_.code) xx >>> print(_('A test message')) N grfg zrffntr >>> with _.using('yy'): ... print(_('A test message')) egassem tset A >>> print(_('A test message')) N grfg zrffntr Substitutions and placeholders ============================== As you can see from the example above, using the library is very simple. You just put the string to translate inside the underscore function. What if your source strings need placeholders for other runtime information? In that case, you use `PEP 292`_ style substitution strings as arguments to the underscore function. Substitutions are taken from the locals and globals of the function doing the translation, so that you don't have to repeat yourself. >>> ordinal = 'first' >>> def print_it(name): ... print(_('The $ordinal test message $name')) In this example, when ``print_it()`` is called, the ``$ordinal`` placeholder is taken from globals, while the ``$name`` placeholder is taken from the function locals (i.e. the arguments). .. >>> # Reset the language context. >>> del _.default >>> print(_.code) None With no language context in place, the source string is printed unchanged, except that the substitutions are made. >>> print_it('Anne') The first test message Anne When a substitution is missing, rather than raise an exception, the ``$variable`` is used unchanged. >>> del ordinal >>> print_it('Bart') The $ordinal test message Bart When there is a language context in effect, the substitutions happen after translation. >>> ordinal = 'second' >>> with _.using('xx'): ... print_it('Cris') second si n grfg zrffntr Cris Some languages change the order of the substitution variables, but of course there is no problem with that. >>> ordinal = 'third' >>> with _.using('yy'): ... print_it('Dave') Dave egassem tset third eht Locals always take precedence over globals:: >>> def print_it(name, ordinal): ... print(_('The $ordinal test message $name')) >>> with _.using('yy'): ... print_it('Elle', 'fourth') Elle egassem tset fourth eht Deferred translations ===================== Sometimes you have a bunch of strings you want to mark for translation, but you want to defer the translation of some of them until later. The way to do this is:: >>> with _.defer_translation(): ... print(_('This gets marked but not translated')) This gets marked but not translated Because the string is wrapped in the ``_()`` function, it will get extracted and added to the catalog, but it will not get translated until later. This is true even if there is a translation context in effect. >>> with _.using('xx'): ... with _.defer_translation(): ... print(_('A test message')) ... print(_('A test message')) A test message N grfg zrffntr .. _`GNU gettext`: http://www.gnu.org/software/gettext/manual/gettext.html .. _`PEP 292`: http://www.python.org/dev/peps/pep-0292/ flufl.i18n-2.0.1/flufl/i18n/NEWS.rst0000664000076500000240000000450313202606366017047 0ustar barrystaff00000000000000===================== NEWS for flufl.i18n ===================== 2.0.1 (2017-11-14) ================== * Restore Python 3.4 support. 2.0 (2017-07-24) ================ * Add ``_.defer_translation()`` context manager for marking, but not translating a string at the point of use. (Closes #2) * Drop Python 2, 3.3, and 3.4 compatibility; add Python 3.5 and 3.6. * Switch to the Apache License Version 2.0 * Use flufl.testing for nose2 and flake8 plugins. 1.1.3 (2014-04-25) ================== * Include MANIFEST.in in the sdist tarball, otherwise the Debian package won't built correctly. 1.1.2 (2014-03-31) ================== * Fix documentation bug. LP: #1026403 * Use modern setuptools rather than distutils. * Bump copyright years. 1.1.1 (2012-04-19) ================== * Add classifiers to setup.py and make the long description more compatible with the Cheeseshop. * Other changes to make the Cheeseshop page look nicer. (LP: #680136) * setup_helper.py version 2.1. 1.1 (2012-01-19) ================ * Support Python 3 without the need for 2to3. 1.0.4 (2010-12-06) ================== * Restore missing line from MANIFEST.in to fix distribution tarball. 1.0.3 (2010-12-01) ================== * Fix setup.py to not install myfixers artifact directory on install. * Remove pylint.rc; we'll use pyflakes instead. 1.0.2 (2010-06-23) ================== * Small documentation fix. 1.0.1 (2010-06-09) ================== * Ditch the use of zc.buildout. * Improved documentation. 1.0 (2010-04-24) ================ * Use Distribute instead of Setuptools. * Port to Python 3 when used with 2to3. * More documentation improvements. 0.6 (2010-04-21) ================ * Documentation and lint clean up. 0.5 (2010-04-20) ================ * Added a simplified initialization API for one-language-context applications. This works much better for non-server applications. * Added a SimpleStrategy which recognizes the $LOCPATH environment variable. * Show how PEP 292 strings are supported automatically. * When strategies are called with zero arguments, they supply the default translation context, which is usually a NullTranslation. This is better than hardcoding the NullTranslation in the Application. 0.4 (2010-03-04) ================ * Add the ability to get the current language code, via _.code 0.3 (2009-11-15) ================ * Initial release. flufl.i18n-2.0.1/flufl/i18n/README.rst0000664000076500000240000000421313202606366017226 0ustar barrystaff00000000000000====================================================== flufl.i18n - A high level API for internationalization ====================================================== This package provides a high level, convenient API for managing internationalization translation contexts in Python application. There is a simple API for single-context applications, such as command line scripts which only need to translate into one language during the entire course of their execution. There is a more flexible, but still convenient API for multi-context applications, such as servers, which may need to switch language contexts for different tasks. Requirements ============ ``flufl.i18n`` requires Python 3.4 or newer. Documentation ============= A `simple guide`_ to using the library is available within this package, in the form of doctests. Project details =============== * Project home: https://gitlab.com/warsaw/flufl.i18n * Report bugs at: https://gitlab.com/warsaw/flufl.i18n/issues * Code hosting: https://gitlab.com/warsaw/flufl.i18n.git * Documentation: https://flufli18n.readthedocs.io/ You can install it with `pip`:: % pip install flufl.i18n You can grab the latest development copy of the code using git. The master repository is hosted on GitLab. If you have git installed, you can grab your own branch of the code like this:: $ git clone https://gitlab.com/warsaw/flufl.i18n.git You may contact the author via barry@python.org. Copyright ========= Copyright (C) 2004-2017 Barry A. Warsaw Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Table of Contents ================= .. toctree:: :glob: docs/using docs/* NEWS .. _`simple guide`: docs/using.html flufl.i18n-2.0.1/flufl/i18n/testing/0000775000076500000240000000000013202606453017211 5ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl/i18n/testing/__init__.py0000664000076500000240000000000013135507413021311 0ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl/i18n/testing/helpers.py0000664000076500000240000000101313135517410021217 0ustar barrystaff00000000000000import os import doctest from contextlib import ExitStack DOCTEST_FLAGS = ( doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE | doctest.REPORT_NDIFF) def setup(testobj): """Test setup.""" testobj.globs['cleanups'] = ExitStack() # Ensure that environment variables affecting translation are neutralized. for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'): if envar in os.environ: del os.environ[envar] def teardown(testobj): testobj.globs['cleanups'].close() flufl.i18n-2.0.1/flufl/i18n/testing/messages/0000775000076500000240000000000013202606453021020 5ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl/i18n/testing/messages/__init__.py0000664000076500000240000000000013135507413023120 0ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl/i18n/testing/messages/xx/0000775000076500000240000000000013202606453021457 5ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl/i18n/testing/messages/xx/LC_MESSAGES/0000775000076500000240000000000013202606453023244 5ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl/i18n/testing/messages/xx/LC_MESSAGES/flufl.mo0000664000076500000240000000077213135507413024720 0ustar barrystaff000000000000004L`ap9 A test messageThe $ordinal test message $nameProject-Id-Version: PACKAGE VERSION POT-Creation-Date: Sun Jan 8 15:53:47 2006 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Transfer-Encoding: 8bit Generated-By: hand N grfg zrffntr$ordinal si n grfg zrffntr $nameflufl.i18n-2.0.1/flufl/i18n/testing/messages/xx/LC_MESSAGES/flufl.po0000664000076500000240000000124113135507413024713 0ustar barrystaff00000000000000# A testing catalog # Copyright (C) 2009-2015 Barry Warsaw # Barry Warsaw , 2009, 2010. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: Sun Jan 8 15:53:47 2006\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=iso-8859-1\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: hand\n" #: flufl/i18n/docs/readme.txt:39 msgid "A test message" msgstr "N grfg zrffntr" #: flufl/i18n/docs/readme.txt:40 msgid "The $ordinal test message $name" msgstr "$ordinal si n grfg zrffntr $name" flufl.i18n-2.0.1/flufl/i18n/testing/messages/yy/0000775000076500000240000000000013202606453021461 5ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl/i18n/testing/messages/yy/LC_MESSAGES/0000775000076500000240000000000013202606453023246 5ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl/i18n/testing/messages/yy/LC_MESSAGES/flufl.mo0000664000076500000240000000077113135507413024721 0ustar barrystaff000000000000004L`ap9A test messageThe $ordinal test message $nameProject-Id-Version: PACKAGE VERSION POT-Creation-Date: Sun Jan 8 15:53:47 2006 PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE Last-Translator: FULL NAME Language-Team: LANGUAGE MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Transfer-Encoding: 8bit Generated-By: hand egassem tset A$name egassem tset $ordinal ehtflufl.i18n-2.0.1/flufl/i18n/testing/messages/yy/LC_MESSAGES/flufl.po0000664000076500000240000000124013135507413024714 0ustar barrystaff00000000000000# A testing catalog # Copyright (C) 2009-2015 Barry Warsaw # Barry Warsaw , 2009, 2010. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: Sun Jan 8 15:53:47 2006\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=iso-8859-1\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: hand\n" #: flufl/i18n/docs/readme.txt:39 msgid "A test message" msgstr "egassem tset A" #: flufl/i18n/docs/readme.txt:40 msgid "The $ordinal test message $name" msgstr "$name egassem tset $ordinal eht" flufl.i18n-2.0.1/flufl/i18n/tests/0000775000076500000240000000000013202606453016676 5ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl/i18n/tests/__init__.py0000664000076500000240000000000013135507413020776 0ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl/i18n/tests/test_application.py0000664000076500000240000000064113135507413022614 0ustar barrystaff00000000000000"""Additional coverage/unittest for the Application class.""" import unittest import flufl.i18n.testing.messages from flufl.i18n import Application, PackageStrategy class TestApplication(unittest.TestCase): def test_application_name(self): strategy = PackageStrategy('flufl', flufl.i18n.testing.messages) application = Application(strategy) self.assertEqual(application.name, 'flufl') flufl.i18n-2.0.1/flufl/i18n/tests/test_expand.py0000664000076500000240000000105313135507413021566 0ustar barrystaff00000000000000"""Test for expand() coverage.""" import unittest from flufl.i18n._expand import expand from unittest.mock import patch class FailingTemplateClass: def __init__(self, template): self.template = template def safe_substitute(self, *args, **kws): raise TypeError class TestExpand(unittest.TestCase): def test_exception(self): with patch('flufl.i18n._expand.log.exception') as log: expand('my-template', {}, FailingTemplateClass) log.assert_called_once_with('broken template: %s', 'my-template') flufl.i18n-2.0.1/flufl/i18n/tests/test_substitute.py0000664000076500000240000000122713135507413022525 0ustar barrystaff00000000000000"""Test for substitute.py coverage.""" import unittest from flufl.i18n._substitute import attrdict from types import SimpleNamespace class TestSubstitute(unittest.TestCase): def test_attrdict_parts(self): ant = dict(bee=SimpleNamespace(cat=SimpleNamespace(dog='elk'))) anteater = attrdict(ant) self.assertEqual(anteater['bee.cat.dog'], 'elk') def test_attrdict_missing(self): ant = dict(bee=SimpleNamespace(cat=SimpleNamespace(dog='elk'))) anteater = attrdict(ant) with self.assertRaises(KeyError) as cm: anteater['bee.cat.doo'] self.assertEqual(str(cm.exception), "'bee.cat.doo'") flufl.i18n-2.0.1/flufl/i18n/tests/test_translator.py0000664000076500000240000000765113135507413022512 0ustar barrystaff00000000000000"""Tests for the Translator class. This cannot be a doctest because of the sys._getframe() manipulations. That does not play well with the way doctest executes Python code. But see translator.txt for a description of how this should work in real Python code. """ import unittest from flufl.i18n._translator import Translator # Some globals for following tests. purple = 'porpoises' magenta = 'monkeys' green = 'gerbil' class Catalog: """Test catalog.""" def __init__(self): self.translation = None def gettext(self, original): """Return the translation.""" return self.translation def charset(self): """Return the encoding.""" # The default is ascii. return None class TestTranslator(unittest.TestCase): """Tests of the Translator class.""" def setUp(self): self.catalog = Catalog() # We need depth=1 because we're calling the translation at the same # level as the locals we care about. self.translator = Translator(self.catalog, depth=1) def test_locals(self): # Test that locals get properly substituted. aqua = 'aardvarks' # noqa: F841 blue = 'badgers' # noqa: F841 cyan = 'cats' # noqa: F841 self.catalog.translation = '$blue and $cyan and $aqua' self.assertEqual(self.translator.translate('source string'), 'badgers and cats and aardvarks') def test_globals(self): # Test that globals get properly substituted. self.catalog.translation = '$purple and $magenta and $green' self.assertEqual(self.translator.translate('source string'), 'porpoises and monkeys and gerbil') def test_dict_overrides_locals(self): # Test that explicit mappings override locals. aqua = 'aardvarks' # noqa: F841 blue = 'badgers' # noqa: F841 cyan = 'cats' # noqa: F841 overrides = dict(blue='bats') self.catalog.translation = '$blue and $cyan and $aqua' self.assertEqual(self.translator.translate('source string', overrides), 'bats and cats and aardvarks') def test_globals_with_overrides(self): # Test that globals with overrides get properly substituted. self.catalog.translation = '$purple and $magenta and $green' overrides = dict(green='giraffe') self.assertEqual(self.translator.translate('source string', overrides), 'porpoises and monkeys and giraffe') def test_empty_string(self): # The empty string is always translated as the empty string. self.assertEqual(self.translator.translate(''), '') def test_dedent(self): # By default, the translated string is always dedented. aqua = 'aardvarks' # noqa: F841 blue = 'badgers' # noqa: F841 cyan = 'cats' # noqa: F841 self.catalog.translation = """\ These are the $blue These are the $cyan These are the $aqua """ for line in self.translator.translate('source string').splitlines(): self.assertTrue(line[:5], 'These') def test_no_dedent(self): # You can optionally suppress the dedent. aqua = 'aardvarks' # noqa: F841 blue = 'badgers' # noqa: F841 cyan = 'cats' # noqa: F841 self.catalog.translation = """\ These are the $blue These are the $cyan These are the $aqua """ translator = Translator(self.catalog, dedent=False) for line in translator.translate('source string').splitlines(): self.assertTrue(line[:9], ' These') flufl.i18n-2.0.1/flufl.i18n.egg-info/0000775000076500000240000000000013202606453017225 5ustar barrystaff00000000000000flufl.i18n-2.0.1/flufl.i18n.egg-info/dependency_links.txt0000664000076500000240000000000113202606453023273 0ustar barrystaff00000000000000 flufl.i18n-2.0.1/flufl.i18n.egg-info/namespace_packages.txt0000664000076500000240000000000613202606453023554 0ustar barrystaff00000000000000flufl flufl.i18n-2.0.1/flufl.i18n.egg-info/not-zip-safe0000664000076500000240000000000113202606453021453 0ustar barrystaff00000000000000 flufl.i18n-2.0.1/flufl.i18n.egg-info/PKG-INFO0000664000076500000240000000174513202606453020331 0ustar barrystaff00000000000000Metadata-Version: 1.1 Name: flufl.i18n Version: 2.0.1 Summary: A high level API for Python internationalization. Home-page: https://flufli18n.readthedocs.io Author: Barry Warsaw Author-email: barry@python.org License: ASLv2 Download-URL: https://pypi.python.org/pypi/flufl.i18n Description-Content-Type: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Internationalization Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Localization flufl.i18n-2.0.1/flufl.i18n.egg-info/requires.txt0000664000076500000240000000001113202606453021615 0ustar barrystaff00000000000000atpublic flufl.i18n-2.0.1/flufl.i18n.egg-info/SOURCES.txt0000664000076500000240000000223313202606453021111 0ustar barrystaff00000000000000MANIFEST.in README.rst coverage.ini setup.cfg setup.py setup_helpers.py tox.ini unittest.cfg flufl/__init__.py flufl.i18n.egg-info/PKG-INFO flufl.i18n.egg-info/SOURCES.txt flufl.i18n.egg-info/dependency_links.txt flufl.i18n.egg-info/namespace_packages.txt flufl.i18n.egg-info/not-zip-safe flufl.i18n.egg-info/requires.txt flufl.i18n.egg-info/top_level.txt flufl/i18n/NEWS.rst flufl/i18n/README.rst flufl/i18n/__init__.py flufl/i18n/_application.py flufl/i18n/_expand.py flufl/i18n/_registry.py flufl/i18n/_strategy.py flufl/i18n/_substitute.py flufl/i18n/_translator.py flufl/i18n/conf.py flufl/i18n/docs/__init__.py flufl/i18n/docs/expand.rst flufl/i18n/docs/strategies.rst flufl/i18n/docs/using.rst flufl/i18n/testing/__init__.py flufl/i18n/testing/helpers.py flufl/i18n/testing/messages/__init__.py flufl/i18n/testing/messages/xx/LC_MESSAGES/flufl.mo flufl/i18n/testing/messages/xx/LC_MESSAGES/flufl.po flufl/i18n/testing/messages/yy/LC_MESSAGES/flufl.mo flufl/i18n/testing/messages/yy/LC_MESSAGES/flufl.po flufl/i18n/tests/__init__.py flufl/i18n/tests/test_application.py flufl/i18n/tests/test_expand.py flufl/i18n/tests/test_substitute.py flufl/i18n/tests/test_translator.pyflufl.i18n-2.0.1/flufl.i18n.egg-info/top_level.txt0000664000076500000240000000000613202606453021753 0ustar barrystaff00000000000000flufl flufl.i18n-2.0.1/MANIFEST.in0000664000076500000240000000015113135520165015400 0ustar barrystaff00000000000000include *.py MANIFEST.in global-include *.txt *.rst *.po *.mo *.ini *.cfg exclude .gitignore prune build flufl.i18n-2.0.1/PKG-INFO0000664000076500000240000000174513202606453014751 0ustar barrystaff00000000000000Metadata-Version: 1.1 Name: flufl.i18n Version: 2.0.1 Summary: A high level API for Python internationalization. Home-page: https://flufli18n.readthedocs.io Author: Barry Warsaw Author-email: barry@python.org License: ASLv2 Download-URL: https://pypi.python.org/pypi/flufl.i18n Description-Content-Type: UNKNOWN Description: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: MacOS :: MacOS X Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Internationalization Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: Software Development :: Localization flufl.i18n-2.0.1/README.rst0000664000076500000240000000150313135517410015332 0ustar barrystaff00000000000000========== flufl.i18n ========== A high level API for Python internationalization. The ``flufl.i18n`` library provides a convenient API for managing translation contexts in Python applications. It provides facilities not only for single-context applications like command line scripts, but also more sophisticated management of multiple-context applications such as Internet servers. Author ====== ``flufl.i18n`` is Copyright (C) 2004-2017 Barry Warsaw Licensed under the terms of the Apache License Version 2.0. See the LICENSE file for details. Project details =============== * Project home: https://gitlab.com/warsaw/flufl.i18n * Report bugs at: https://gitlab.com/warsaw/flufl.i18n/issues * Code hosting: https://gitlab.com/warsaw/flufl.i18n.git * Documentation: https://flufli18n.readthedocs.io/ flufl.i18n-2.0.1/setup.cfg0000664000076500000240000000011613202606453015464 0ustar barrystaff00000000000000[build_sphinx] source_dir = flufl/i18n [egg_info] tag_build = tag_date = 0 flufl.i18n-2.0.1/setup.py0000664000076500000240000000245513202602000015345 0ustar barrystaff00000000000000from setup_helpers import description, get_version, require_python from setuptools import setup, find_packages require_python(0x30400f0) __version__ = get_version('flufl/i18n/__init__.py') setup( name='flufl.i18n', version=__version__, namespace_packages=['flufl'], packages=find_packages(), include_package_data=True, zip_safe=False, maintainer='Barry Warsaw', maintainer_email='barry@python.org', description=description('README.rst'), license='ASLv2', url='https://flufli18n.readthedocs.io', download_url='https://pypi.python.org/pypi/flufl.i18n', install_requires=[ 'atpublic', ], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', 'Operating System :: MacOS :: MacOS X', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Topic :: Software Development :: Internationalization', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Localization', ] ) flufl.i18n-2.0.1/setup_helpers.py0000664000076500000240000001067313135517410017107 0ustar barrystaff00000000000000# Copyright 2004-2017 Barry Warsaw """setup.py helper functions.""" from __future__ import absolute_import, print_function, unicode_literals __metaclass__ = type __all__ = [ 'description', 'find_doctests', 'get_version', 'long_description', 'require_python', ] import os import re import sys DEFAULT_VERSION_RE = re.compile( r'(?P\d+\.\d+(?:\.\d+)?(?:(?:a|b|rc)\d+)?)') EMPTYSTRING = '' __version__ = '3.0' def require_python(minimum): """Require at least a minimum Python version. The version number is expressed in terms of `sys.hexversion`. E.g. to require a minimum of Python 2.6, use:: >>> require_python(0x206000f0) :param minimum: Minimum Python version supported. :type minimum: integer """ if sys.hexversion < minimum: hversion = hex(minimum)[2:] if len(hversion) % 2 != 0: hversion = '0' + hversion split = list(hversion) parts = [] while split: parts.append(int(''.join((split.pop(0), split.pop(0))), 16)) major, minor, micro, release = parts if release == 0xf0: print('Python {0}.{1}.{2} or better is required'.format( major, minor, micro)) else: print('Python {0}.{1}.{2} ({3}) or better is required'.format( major, minor, micro, hex(release)[2:])) sys.exit(1) def get_version(filename, pattern=None): """Extract the __version__ from a file without importing it. While you could get the __version__ by importing the module, the very act of importing can cause unintended consequences. For example, Distribute's automatic 2to3 support will break. Instead, this searches the file for a line that starts with __version__, and extract the version number by regular expression matching. By default, two or three dot-separated digits are recognized, but by passing a pattern parameter, you can recognize just about anything. Use the `version` group name to specify the match group. :param filename: The name of the file to search. :type filename: string :param pattern: Optional alternative regular expression pattern to use. :type pattern: string :return: The version that was extracted. :rtype: string """ if pattern is None: cre = DEFAULT_VERSION_RE else: cre = re.compile(pattern) with open(filename) as fp: for line in fp: if line.startswith('__version__'): mo = cre.search(line) assert mo, 'No valid __version__ string found' return mo.group('version') raise AssertionError('No __version__ assignment found') def find_doctests(start='.', extension='.rst'): """Find separate-file doctests in the package. This is useful for Distribute's automatic 2to3 conversion support. The `setup()` keyword argument `convert_2to3_doctests` requires file names, which may be difficult to track automatically as you add new doctests. :param start: Directory to start searching in (default is cwd) :type start: string :param extension: Doctest file extension (default is .txt) :type extension: string :return: The doctest files found. :rtype: list """ doctests = [] for dirpath, dirnames, filenames in os.walk(start): doctests.extend(os.path.join(dirpath, filename) for filename in filenames if filename.endswith(extension)) return doctests def long_description(*filenames): """Provide a long description.""" res = [''] for filename in filenames: with open(filename) as fp: for line in fp: res.append(' ' + line) res.append('') res.append('\n') return EMPTYSTRING.join(res) def description(filename): """Provide a short description.""" # This ends up in the Summary header for PKG-INFO and it should be a # one-liner. It will get rendered on the package page just below the # package version header but above the long_description, which ironically # gets stuff into the Description header. It should not include reST, so # pick out the first single line after the double header. with open(filename) as fp: for lineno, line in enumerate(fp): if lineno < 3: continue line = line.strip() if len(line) > 0: return line flufl.i18n-2.0.1/tox.ini0000664000076500000240000000236113202602000015142 0ustar barrystaff00000000000000[tox] envlist = {py34,py35,py36}-{cov,nocov,diffcov},qa,docs recreate = True skip_missing_interpreters = True [testenv] commands = nocov: python -m nose2 -v {posargs} {cov,diffcov}: python -m coverage run {[coverage]rc} -m nose2 -v {cov,diffcov}: python -m coverage combine {[coverage]rc} cov: python -m coverage html {[coverage]rc} cov: python -m coverage report -m {[coverage]rc} --fail-under=100 diffcov: python -m coverage xml {[coverage]rc} diffcov: diff-cover coverage.xml --html-report diffcov.html diffcov: diff-cover coverage.xml --fail-under=100 #sitepackages = True usedevelop = True deps = nose2 flufl.testing {cov,diffcov}: coverage diffcov: diff_cover setenv = cov: COVERAGE_PROCESS_START={[coverage]rcfile} cov: COVERAGE_OPTIONS="-p" cov: COVERAGE_FILE={toxinidir}/.coverage passenv = PYTHON* [coverage] rcfile = {toxinidir}/coverage.ini rc = --rcfile={[coverage]rcfile} [testenv:qa] basepython = python3 commands = python -m flake8 flufl/i18n deps = flake8 flufl.testing [testenv:docs] basepython = python3 commands = python setup.py build_sphinx deps: sphinx [flake8] enable-extensions = U4 exclude = conf.py hang-closing = True jobs = 1 max-line-length = 79 flufl.i18n-2.0.1/unittest.cfg0000664000076500000240000000040313135517410016201 0ustar barrystaff00000000000000[unittest] verbose = 2 plugins = flufl.testing.nose nose2.plugins.layers [log-capture] always-on = False [flufl.testing] always-on = True package = flufl.i18n setup = flufl.i18n.testing.helpers.setup teardown = flufl.i18n.testing.helpers.teardown