├── LICENCE ├── README.md ├── README.rst ├── demo ├── demo.py ├── templates │ └── locale.html └── translations │ └── tr_TR.csv ├── flask_locale ├── __init__.py └── version.py ├── requirements.txt ├── setup.cfg └── setup.py /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 by Erkan Durmus. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are 7 | met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask-Locale 2 | 3 | Implements i18n and l10n support for Flask. This is based on the old [Flask-Locale](http://github.com/whtsky/whtsky-locale/) extension. Uses files or database to get translations. 4 | 5 | You can use this extension to translate your applications really easily. No babel preperation is needed. Just put your English text and its translation in a file. 6 | 7 | ## Install 8 | 9 | ```sh 10 | pip install Flask-Locale 11 | ``` 12 | 13 | ## Quick Start 14 | - Py3 ready 15 | - For very quick test look at `demo` directory. 16 | 17 | - Create a directory `translations` at app root. 18 | - Create file `translations/tr_TR.csv` with this content: 19 | 20 | ```csv 21 | "Hello %(name)s","Merhaba %(name)s" 22 | "Hello","Merhaba" 23 | ``` 24 | - Create `templates` directory at app root. 25 | 26 | - Create `locale.html` file with this content: 27 | 28 | ```html 29 | 30 | 31 | 32 | 33 | Flask-Locale 34 | 35 | 36 |

Translate with parameters in template

37 | {{ _('Hello %(name)s', name=name) }} 38 |
39 |

Translated in Python Code:

40 | {{ py_translated }} 41 | 42 | 43 | ``` 44 | -- Create your application main file `demo.py`: 45 | 46 | ```py 47 | # -*- coding: utf-8 -*- 48 | 49 | from flask import Flask, request, render_template, g 50 | from flask_locale import Locale, _ 51 | 52 | app = Flask(__name__) 53 | app.config['LOCALE_PATH'] = 'translations' 54 | 55 | locale = Locale(app) 56 | 57 | 58 | @locale.localeselector 59 | def get_locale(): 60 | # if a user is logged in, use the locale from the user settings 61 | user = getattr(g, 'user', None) 62 | if user is not None: 63 | return user.locale 64 | # otherwise try to guess the language from the user accept 65 | # header the browser transmits. We support tr/fr/en in this 66 | # example. The best match wins. 67 | return request.accept_languages.best_match(['tr_TR', 'fr_FR', 'en_US']) 68 | 69 | 70 | @app.route("/") 71 | def index(): 72 | # How we do translation in python code: 73 | py_translated = _('Hello') 74 | # How we do translation in template: 75 | return render_template('locale.html', name='Erkan', py_translated=py_translated) 76 | 77 | 78 | if __name__ == '__main__': 79 | app.run(debug=True) 80 | 81 | ``` 82 | 83 | - Run yout app: 84 | 85 | ```sh 86 | python demo.py 87 | ``` 88 | 89 | - Now access your app: `http://127.0.0.1:5000/` 90 | 91 | ## Usage 92 | 93 | ### Loading translations from a file: 94 | Loads translations from CSV files having locale extension in a directory. File should be `utf-8` encoded. 95 | 96 | Translations are strings with optional Python-style named placeholders (e.g., ``"My name is %(name)s"``) and their associated translations. 97 | 98 | The directory should have translation files of the form filename: LOCALE, e.g. tr_TR. 99 | 100 | Translation files should have two or three columns: string, translation, and an optional plural indicator. Plural indicators should be one of ``"plural"`` or ``"singular"``. 101 | 102 | A given string can have both singular and plural forms. For example ``"%(name)s liked this"`` may have a different verb conjugation depending on whether %(name)s is one name or a list of names. There should be two rows in the CSV file for that string, one with plural indicator "singular", and one "plural". 103 | 104 | For strings with no verbs that would change on translation, simply 105 | use ``"unknown"`` or the empty string (or don't include the column at all). 106 | 107 | The file is read using the csv module in the default "excel" dialect. 108 | In this format there should not be spaces after the commas. 109 | 110 | Example translation tr_TR.csv:: 111 | 112 | ``` 113 | "I love you","Seni seviyorum" 114 | "%(name)s liked these","A %(name)s bunları sevdi","plural" 115 | "%(name)s liked this","A %(name)s bunu sevdi","singular" 116 | ``` 117 | 118 | ### Loading translations from database: 119 | 120 | ```py 121 | @locale.db_loader 122 | def get_translations(): 123 | """Translations selector for db""" 124 | sql = select( 125 | [Locale.c.code, TranslationKey.c.name, Translation.c.translated, Translation.c.singular], 126 | from_obj=[Locale.join(Translation).join(TranslationKey)]) 127 | q = db.session.execute(sql) 128 | data = q.fetchall() 129 | q.close() 130 | return list(data) 131 | ``` 132 | 133 | ### Reloading translations 134 | 135 | When user's locale is changed, call `refresh()` method: 136 | 137 | ```py 138 | user.locale = request.form['locale'] 139 | locale.refresh() 140 | flash(_('Language is changed')) 141 | ``` 142 | 143 | ### Translate Functions 144 | 145 | `translate()` (or its alias `_()`) method does a lazy translation, that means its actual translate function is called when you access it. So you can use translate functions in your forms before Flask-Locale is initialized. 146 | 147 | 148 | ```py 149 | from flask.ext.wtf import Form 150 | from wtforms.fields import TextField, PasswordField 151 | from wtforms.validators import Required, Email 152 | from extensions import _ 153 | 154 | class EmailPasswordForm(Form): 155 | email = TextField(_('Email'), validators=[Required(), Email()]) 156 | password = PasswordField(_('Password'), validators=[Required()]) 157 | ``` 158 | 159 | If you want immediate translation, use `do_translate` method. 160 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Flask-Locale 2 | ================= 3 | 4 | Implements i18n and l10n support for Flask. This is based on the old [Flask-Locale](http://github.com/whtsky/whtsky-locale/) extension. Uses files or database to get translations. 5 | 6 | You can use this extension to translate your applications really easily. No babel preperation is needed. Just put your English text and its translation in a file. 7 | 8 | Install 9 | ------- 10 | 11 | pip install Flask-Locale 12 | 13 | 14 | Quick Start 15 | ----------- 16 | 17 | - Py3 ready 18 | - For very quick test look at `demo` directory. 19 | 20 | - Create a directory `translations` at app root. 21 | - Create file `translations/tr_TR.csv` with this content: 22 | 23 | 24 | "Hello %(name)s","Merhaba %(name)s" 25 | 26 | "Hello","Merhaba" 27 | 28 | - Create `templates` directory at app root. 29 | 30 | - Create `locale.html` file with this content: 31 | 32 | >>> 33 | 34 | 35 | 36 | 37 | Flask-Locale 38 | 39 | 40 |

Translate with parameters in template

41 | {{ _('Hello %(name)s', name=name) }} 42 |
43 |

Translated in Python Code:

44 | {{ py_translated }} 45 | 46 | 47 | 48 | -- Create your application main file `demo.py`: 49 | 50 | >>> 51 | # -*- coding: utf-8 -*- 52 | from flask import Flask, request, render_template, g 53 | from flask_locale import Locale, _ 54 | app = Flask(__name__) 55 | app.config['LOCALE_PATH'] = 'translations' 56 | locale = Locale(app) 57 | @locale.localeselector 58 | def get_locale(): 59 | # if a user is logged in, use the locale from the user settings 60 | user = getattr(g, 'user', None) 61 | if user is not None: 62 | return user.locale 63 | # otherwise try to guess the language from the user accept 64 | # header the browser transmits. We support tr/fr/en in this 65 | # example. The best match wins. 66 | return request.accept_languages.best_match(['tr_TR', 'fr_FR', 'en_US']) 67 | @app.route("/") 68 | def index(): 69 | # How we do translation in python code: 70 | py_translated = _('Hello') 71 | # How we do translation in template: 72 | return render_template('locale.html', name='Erkan', 73 | py_translated=py_translated) 74 | if __name__ == '__main__': 75 | app.run(debug=True) 76 | 77 | - Run your app: 78 | 79 | >>> python demo.py 80 | 81 | 82 | - Now access your app: `http://127.0.0.1:5000/` 83 | 84 | Usage 85 | ----- 86 | 87 | **Loading translations from a file:** 88 | 89 | Loads translations from CSV files having locale extension in a directory. File should be `utf-8` encoded. 90 | 91 | Translations are strings with optional Python-style named placeholders (e.g., ``"My name is %(name)s"``) and their associated translations. 92 | 93 | The directory should have translation files of the form filename: LOCALE, e.g. tr_TR. 94 | 95 | Translation files should have two or three columns: string, translation, and an optional plural indicator. Plural indicators should be one of ``"plural"`` or ``"singular"``. 96 | 97 | A given string can have both singular and plural forms. For example ``"%(name)s liked this"`` may have a different verb conjugation depending on whether %(name)s is one name or a list of names. There should be two rows in the CSV file for that string, one with plural indicator "singular", and one "plural". 98 | 99 | For strings with no verbs that would change on translation, simply 100 | use ``"unknown"`` or the empty string (or don't include the column at all). 101 | 102 | The file is read using the csv module in the default "excel" dialect. 103 | In this format there should not be spaces after the commas. 104 | 105 | Example translation tr_TR.csv: 106 | 107 | >>> 108 | "I love you","Seni seviyorum" 109 | "%(name)s liked these","A %(name)s bunları sevdi","plural" 110 | "%(name)s liked this","A %(name)s bunu sevdi","singular" 111 | 112 | 113 | **Loading translations from database:** 114 | 115 | 116 | >>> 117 | @locale.db_loader 118 | def get_translations(): 119 | """Translations selector for db""" 120 | sql = select( 121 | [Locale.c.code, TranslationKey.c.name, Translation.c.translated, Translation.c.singular], 122 | from_obj=[Locale.join(Translation).join(TranslationKey)]) 123 | q = db.session.execute(sql) 124 | data = q.fetchall() 125 | q.close() 126 | return list(data) 127 | 128 | 129 | **Reloading translations** 130 | 131 | When user's locale is changed, call `refresh()` method: 132 | 133 | >>> 134 | user.locale = request.form['locale'] 135 | locale.refresh() 136 | flash(_('Language is changed')) 137 | 138 | **Translate Functions** 139 | 140 | `translate()` (or its alias `_()`) method does a lazy translation, that means its actual translate function is called when you access it. So you can use translate functions in your forms before Flask-Locale is initialized. 141 | 142 | 143 | >>> 144 | from flask.ext.wtf import Form 145 | from wtforms.fields import TextField, PasswordField 146 | from wtforms.validators import Required, Email 147 | from extensions import _ 148 | class EmailPasswordForm(Form): 149 | email = TextField(_('Email'), validators=[Required(), Email()]) 150 | password = PasswordField(_('Password'), validators=[Required()]) 151 | 152 | 153 | If you want immediate translation, use `do_translate` method. 154 | -------------------------------------------------------------------------------- /demo/demo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import Flask, request, render_template, g, session, redirect, current_app 4 | from flask_locale import Locale, _ 5 | 6 | app = Flask(__name__) 7 | # DEFAULT_LOCALE is the language used for keys ins translation files: 8 | app.config['DEFAULT_LOCALE'] = 'tr_TR' 9 | app.config['LOCALE_PATH'] = 'translations' 10 | app.config['SECRET_KEY'] = 'translations****' 11 | 12 | locale = Locale(app) 13 | 14 | 15 | @locale.localeselector 16 | def get_locale(): 17 | # if a user is logged in, use the locale from the session 18 | # define a default value instead of None to set it to specific locale if not setting is found. 19 | locale_code = session.get('locale', None) 20 | if locale_code is not None: 21 | current_app.logger.info("Locale is: %s" % locale_code) 22 | return locale_code 23 | 24 | # otherwise try to guess the language from the user accept 25 | # header the browser transmits. We support tr/fr/en in this 26 | # example. The best match wins. 27 | locale_code = request.accept_languages.best_match(['tr_TR', 'fr_FR', 'en_US']) 28 | current_app.logger.info("Locale match: %s" % locale_code) 29 | return locale_code 30 | 31 | 32 | @app.route("/") 33 | def index(): 34 | # How we do translation in python code: 35 | py_translated = _('Hello') 36 | # How we do translation in template: 37 | return render_template('locale.html', name='Erkan', py_translated=py_translated) 38 | 39 | 40 | @app.route("/locale") 41 | def change_locale(): 42 | new_locale = request.args.get('locale', None) 43 | session['locale'] = new_locale 44 | return redirect('/') 45 | 46 | 47 | if __name__ == '__main__': 48 | app.run(debug=True) 49 | -------------------------------------------------------------------------------- /demo/templates/locale.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flask-Locale 6 | 7 | 8 |

Translate with parameters in template

9 | {{ _('Hello %(name)s', name=name) }} 10 |
11 |

Translated in Python Code:

12 | {{ py_translated }} 13 | 14 | -------------------------------------------------------------------------------- /demo/translations/tr_TR.csv: -------------------------------------------------------------------------------- 1 | "Hello %(name)s","Merhaba %(name)s" 2 | "Hello","Merhaba" -------------------------------------------------------------------------------- /flask_locale/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Flask-Locale 4 | ---- 5 | 6 | 7 | Some code is from Tornado.Locale 8 | """ 9 | 10 | import os 11 | import sys 12 | import re 13 | import csv 14 | import unicodedata 15 | 16 | from flask import _request_ctx_stack, request 17 | from speaklater import make_lazy_string 18 | 19 | __all__ = ('Locale', 'refresh', 'translate', 'to_unicode', '_', 'do_translate') 20 | is_python3 = sys.version_info.major == 3 21 | if is_python3: 22 | unicode = str 23 | 24 | 25 | def get_app(): 26 | ctx = _request_ctx_stack.top 27 | if not ctx: 28 | return None 29 | return ctx.app 30 | 31 | 32 | def to_unicode(value): 33 | """Converts a string argument to a unicode string. 34 | 35 | If the argument is already a unicode string or None, it is returned 36 | unchanged. Otherwise it must be a byte string and is decoded as utf8. 37 | """ 38 | if isinstance(value, unicode): 39 | return value 40 | assert isinstance(value, bytes) 41 | return value.decode("utf-8") 42 | 43 | 44 | class Locale(object): 45 | """Central controller class that can be used to configure how 46 | Flask-Locale behaves. Each application that wants to use Flask-Locale 47 | has to create, or run :meth:`init_app` on, an instance of this class 48 | after the configuration was initialized. 49 | """ 50 | 51 | def __init__(self, app=None, default_locale='en_US', configure_jinja=True): 52 | self._default_locale = default_locale 53 | self._configure_jinja = configure_jinja 54 | self._supported_locales = frozenset(default_locale) 55 | self._translations = None 56 | self.db_loader_func = None 57 | self.locale_selector_func = None 58 | self.app = None 59 | if app: 60 | self.init_app(app) 61 | 62 | @staticmethod 63 | def refresh(): 64 | refresh() 65 | 66 | def init_app(self, app): 67 | """Set up this instance for use with *app*, if no app was passed to 68 | the constructor. 69 | """ 70 | self.app = app 71 | if not hasattr(app, 'extensions'): 72 | app.extensions = {} 73 | app.extensions['locale'] = self 74 | 75 | app.config.setdefault('DEFAULT_LOCALE', self._default_locale) 76 | locale_path = os.path.join(app.root_path, 'translations') 77 | app.config.setdefault('LOCALE_PATH', locale_path) 78 | 79 | if self._configure_jinja: 80 | app.jinja_env.add_extension('jinja2.ext.i18n') 81 | app.jinja_env.install_gettext_callables( 82 | translate, 83 | translate, 84 | newstyle=True 85 | ) 86 | 87 | def load_translations(self, directory): 88 | """Loads translations from CSV files having locale extension in a directory. 89 | 90 | Translations are strings with optional Python-style named placeholders 91 | (e.g., ``"My name is %(name)s"``) and their associated translations. 92 | 93 | The directory should have translation files of the form filename: LOCALE, 94 | e.g. common.es_GT. Translation files should have two or three columns: string, 95 | translation, and an optional plural indicator. Plural indicators should 96 | be one of ``"plural"`` or ``"singular"``. A given string can have both singular 97 | and plural forms. For example ``"%(name)s liked this"`` may have a 98 | different verb conjugation depending on whether %(name)s is one 99 | name or a list of names. There should be two rows in the CSV file for 100 | that string, one with plural indicator "singular", and one "plural". 101 | For strings with no verbs that would change on translation, simply 102 | use ``"unknown"`` or the empty string (or don't include the column at all). 103 | 104 | The file is read using the csv module in the default "excel" dialect. 105 | In this format there should not be spaces after the commas. 106 | 107 | Example translation es_LA.csv:: 108 | 109 | "I love you","Te amo" 110 | 111 | "%(name)s liked this","A %(name)s les gust\u00f3 esto","plural" 112 | 113 | "%(name)s liked this","A %(name)s le gust\u00f3 esto","singular" 114 | """ 115 | app = get_app() 116 | logger = app.logger 117 | _translations = {} 118 | 119 | if getattr(self, 'db_loader_func', None): 120 | trans = self.db_loader_func() 121 | for row in trans: 122 | locale = row[0] 123 | locale = unicodedata.normalize('NFKD', locale).encode('ascii', 'ignore') 124 | if locale not in _translations: 125 | _translations[locale] = {} 126 | 127 | if not row or len(row) < 3: 128 | continue 129 | plural = ('plural' if row[2] == False else 'unknown') or "unknown" 130 | row = [to_unicode(c).strip() for c in row[1:3]] 131 | english, translation = row[:2] 132 | english = unicodedata.normalize('NFKD', english).encode('ascii', 'ignore') 133 | if plural not in ("plural", "singular", "unknown"): 134 | logger.error("Unrecognized plural '%s' indicator for %s", plural, english) 135 | continue 136 | 137 | _translations[locale].setdefault(plural, {})[english] = translation 138 | else: 139 | for path in os.listdir(directory): 140 | locale, ext = path.split(".") 141 | if not re.match("[a-z]+(_[A-Z]+)?$", locale): 142 | logger.error("Unrecognized locale %r (path: %s)", locale, 143 | os.path.join(directory, path)) 144 | continue 145 | full_path = os.path.join(directory, path) 146 | try: 147 | # python 3: csv.reader requires a file open in text mode. 148 | # Force utf8 to avoid dependence on $LANG environment variable. 149 | f = open(full_path, "r", encoding="utf-8") 150 | except TypeError: 151 | # python 2: files return byte strings, which are decoded below. 152 | # Once we drop python 2.5, this could use io.open instead 153 | # on both 2 and 3. 154 | f = open(full_path, "r") 155 | 156 | if locale not in _translations: 157 | _translations[locale] = {} 158 | 159 | for i, row in enumerate(csv.reader(f, delimiter=',', skipinitialspace=True)): 160 | if not row or len(row) < 2: 161 | continue 162 | row = [to_unicode(c).strip() for c in row] 163 | english, translation = row[:2] 164 | if len(row) > 2: 165 | plural = row[2] or "unknown" 166 | else: 167 | plural = "unknown" 168 | if plural not in ("plural", "singular", "unknown"): 169 | logger.error("Unrecognized plural indicator %r in %s line %d", 170 | plural, path, i + 1) 171 | continue 172 | _translations[locale].setdefault(plural, {})[english] = translation 173 | f.close() 174 | _supported_locales = frozenset(_translations.keys()) 175 | self._supported_locales = _supported_locales 176 | self._translations = _translations 177 | 178 | def db_loader(self, f): 179 | """Registers a callback function for loading translations 180 | """ 181 | assert not getattr(self, 'db_loader_func', None), 'a db_loader function is already registered' 182 | self.db_loader_func = f 183 | return f 184 | 185 | def localeselector(self, f): 186 | """Registers a callback function for locale selection. The default 187 | behaves as if a function was registered that returns `None` all the 188 | time. If `None` is returned, the locale falls back to the one from 189 | the configuration. 190 | 191 | This has to return the locale as string (eg: ``'de_AT'``, ``'en_US'``) 192 | """ 193 | assert getattr(self, 'locale_selector_func', None) is None, 'a localeselector function is already registered' 194 | self.locale_selector_func = f 195 | return f 196 | 197 | def get_browser_locale(self): 198 | """Determines the user's locale from Accept-Language header. 199 | 200 | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 201 | """ 202 | 203 | def get_loc_key(l, s): 204 | return s 205 | 206 | if "Accept-Language" in request.headers: 207 | languages = request.headers["Accept-Language"].split(",") 208 | locales = [] 209 | for language in languages: 210 | parts = language.strip().split(";") 211 | if len(parts) > 1 and parts[1].startswith("q="): 212 | try: 213 | score = float(parts[1][2:]) 214 | except (ValueError, TypeError): 215 | score = 0.0 216 | else: 217 | score = 1.0 218 | locales.append((parts[0], score)) 219 | if locales: 220 | locales.sort(reverse=True) 221 | codes = [l[0] for l in locales] 222 | return self.get_closest(*codes) 223 | app = get_app() 224 | return self.get_closest(app.config['DEFAULT_LOCALE']) 225 | 226 | def get_closest(self, *locale_codes): 227 | """Returns the closest match for the given locale code.""" 228 | app = get_app() 229 | if self._translations is None: 230 | self.load_translations(app.config['LOCALE_PATH']) 231 | 232 | for code in locale_codes: 233 | if not code: 234 | continue 235 | code = code.replace("-", "_") 236 | parts = code.split("_") 237 | if code in self._supported_locales: 238 | return self.get(code) 239 | for c in self._supported_locales: 240 | if c.startswith(parts[0].lower()+'_'): 241 | return self.get(c) 242 | return self.get(app.config['DEFAULT_LOCALE']) 243 | 244 | def get(self, code): 245 | """Returns the translate dict for the given locale code.""" 246 | return self._translations.get(code, {}) 247 | 248 | 249 | def get_translation(): 250 | """Returns the correct gettext translations that should be used for 251 | this request. This will never fail and return a dummy translation 252 | object if used outside of the request or if a translation cannot be 253 | found. 254 | """ 255 | app = get_app() 256 | if not app: 257 | return None 258 | locale_instance = app.extensions['locale'] 259 | locale = None 260 | if getattr(locale_instance, 'locale_selector_func', None): 261 | rv = locale_instance.locale_selector_func() 262 | if rv is not None: 263 | locale = locale_instance.get_closest(rv) 264 | if locale is None: 265 | locale = locale_instance.get_browser_locale() 266 | return locale 267 | 268 | 269 | def refresh(): 270 | """Refreshes the cached locale information. This can be used to switch 271 | a translation between a request and if you want the changes to take place 272 | immediately, not just with the next request:: 273 | 274 | user.locale = request.form['locale'] 275 | refresh() 276 | flash(translate('Language was changed')) 277 | 278 | Without that refresh, the :func:`~flask.flash` function would probably 279 | return English text and a now German page. 280 | """ 281 | app = get_app() 282 | if not app: 283 | return None 284 | app.extensions['locale']._translations = None 285 | 286 | 287 | def translate(message, **variables): 288 | return make_lazy_string(do_translate, message, **variables) 289 | 290 | 291 | _ = translate 292 | 293 | 294 | def do_translate(message, plural_message=None, count=None): 295 | """Returns the translation for the given message for this locale. 296 | 297 | If plural_message is given, you must also provide count. We return 298 | plural_message when count != 1, and we return the singular form 299 | for the given message when count == 1. 300 | """ 301 | translation = get_translation() 302 | if plural_message is not None: 303 | assert count 304 | if count != 1: 305 | message = plural_message 306 | message_dict = translation.get("plural", {}) 307 | else: 308 | message_dict = translation.get("singular", {}) 309 | else: 310 | message_dict = translation.get("singular", None) 311 | if message_dict is None: 312 | message_dict = translation.get("unknown", {}) 313 | return str(message_dict.get(message, message)) 314 | -------------------------------------------------------------------------------- /flask_locale/version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.0.5' 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | speaklater 2 | Flask 3 | speaklater -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Flask-Locale 3 | ----------- 4 | 5 | Adds i18n/l10n support to Flask applications. 6 | """ 7 | from setuptools import setup 8 | 9 | setup( 10 | name='Flask-Locale', 11 | version='1.0.4', 12 | url='http://github.com/derkan/flask-locale', 13 | license='BSD', 14 | author='Erkan Durmus', 15 | author_email='derkan@gmail.com', 16 | description='Adds i18n/l10n support to Flask applications easily. Uses CSV files(or database) to load translations.', 17 | long_description=open('README.rst').read(), 18 | packages=['flask_locale'], 19 | zip_safe=False, 20 | platforms='any', 21 | install_requires=[ 22 | 'Flask', 23 | 'speaklater' 24 | ], 25 | classifiers=[ 26 | 'Environment :: Web Environment', 27 | 'Intended Audience :: Developers', 28 | 'License :: OSI Approved :: BSD License', 29 | 'Operating System :: OS Independent', 30 | 'Programming Language :: Python', 31 | 'Programming Language :: Python :: 3', 32 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 33 | 'Topic :: Software Development :: Libraries :: Python Modules' 34 | ] 35 | ) 36 | --------------------------------------------------------------------------------