utils: Rewrite i18n initialization

The previous implementation was messy and needlessly complicated

This simplifies the logic and removes hackiness by making utils/str.py
handle internationalization logic itself, instead of bending over
backwards to load logic from the parent package at import time.
This commit is contained in:
Valentin Lorentz 2022-06-16 23:43:34 +02:00 committed by Val Lorentz
parent 4a620bf7f0
commit 93370b6f0e
5 changed files with 52 additions and 52 deletions

View File

@ -33,10 +33,7 @@ from . import dynamicScope
from . import i18n
builtins = (__builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__)
builtins['supybotInternationalization'] = i18n.PluginInternationalization()
from . import utils
del builtins['supybotInternationalization']
(__builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__)['format'] = utils.str.format

View File

@ -113,7 +113,6 @@ def getLocalePath(name, localeName, extension):
i18nClasses = weakref.WeakValueDictionary()
internationalizedCommands = weakref.WeakValueDictionary()
internationalizedFunctions = [] # No need to know their name
def reloadLocalesIfRequired():
global currentLocale
@ -124,12 +123,13 @@ def reloadLocalesIfRequired():
reloadLocales()
def reloadLocales():
import supybot.utils as utils
for pluginClass in i18nClasses.values():
pluginClass.loadLocale()
for command in list(internationalizedCommands.values()):
internationalizeDocstring(command)
for function in internationalizedFunctions:
function.loadLocale()
utils.str._relocalizeFunctions(PluginInternationalization())
def normalize(string, removeNewline=False):
import supybot.utils as utils
@ -201,7 +201,6 @@ def parse(translationFile):
return translations
i18nSupybot = None
def PluginInternationalization(name='supybot'):
# This is a proxy that prevents having several objects for the same plugin
if name in i18nClasses:
@ -326,40 +325,6 @@ class _PluginInternationalization:
name in self._l10nFunctions:
return self._l10nFunctions[name]
def internationalizeFunction(self, name):
"""Decorates functions and internationalize their code.
Only useful for Supybot core functions"""
if self.name != 'supybot':
return
class FunctionInternationalizer:
def __init__(self, parent, name):
self._parent = parent
self._name = name
def __call__(self, obj):
obj = InternationalizedFunction(self._parent, self._name, obj)
obj.loadLocale()
return obj
return FunctionInternationalizer(self, name)
class InternationalizedFunction:
"""Proxy for functions that need to be fully localized.
The localization code is in locales/LOCALE.py"""
def __init__(self, internationalizer, name, function):
self._internationalizer = internationalizer
self._name = name
self._origin = function
internationalizedFunctions.append(self)
def loadLocale(self):
self.__call__ = self._internationalizer.localizeFunction(self._name)
if self.__call__ == None:
self.restore()
def restore(self):
self.__call__ = self._origin
def __call__(self, *args, **kwargs):
return self._origin(*args, **kwargs)
try:
class InternationalizedString(str):
@ -375,6 +340,7 @@ except TypeError:
know if a string is already localized"""
pass
def internationalizeDocstring(obj):
"""Decorates functions and internationalize their docstring.
@ -391,3 +357,13 @@ def internationalizeDocstring(obj):
# attribute '__doc__' of 'type' objects is not writable
pass
return obj
def _install():
from . import utils
_ = PluginInternationalization()
utils.gen._ = _
utils.str._ = _
utils.str._relocalizeFunctions(_)
_install()

View File

@ -50,6 +50,7 @@ csv.split = split
builtins = (__builtins__ if isinstance(__builtins__, dict) else __builtins__.__dict__)
# We use this often enough that we're going to stick it in builtins.
def force(x):
if callable(x):
@ -58,8 +59,6 @@ def force(x):
return x
builtins['force'] = force
internationalization = builtins.get('supybotInternationalization', None)
# These imports need to happen below the block above, so things get put into
# __builtins__ appropriately.
from .gen import *

View File

@ -45,7 +45,9 @@ from . import crypt
from .str import format
from .file import mktemp
from . import minisix
from . import internationalization as _
# will be replaced by supybot.i18n.install()
_ = lambda x: x
def warn_non_constant_time(f):
@functools.wraps(f)

View File

@ -38,20 +38,46 @@ import sys
import time
import string
import textwrap
import functools
from . import minisix
from .iter import any
from .structures import TwoWayDictionary
from . import internationalization as _
internationalizeFunction = _.internationalizeFunction
try:
from charade.universaldetector import UniversalDetector
charadeLoaded = True
except ImportError:
charadeLoaded = False
# will be replaced by supybot.i18n.install()
_ = lambda x: x
# used by supybot.i18n.reloadLocales() to (re)load the localized function of
# these functions
_localizedFunctions = {}
_defaultFunctions = {}
def internationalizeFunction(f):
f_name = f.__name__
_localizedFunctions[f_name] = f
_defaultFunctions[f_name] = f
@functools.wraps(f)
def newf(*args, **kwargs):
f = _localizedFunctions[f_name]
assert f is not None, "_localizedFunctions[%s] is None" % f_name
return f(*args, **kwargs)
return newf
def _relocalizeFunctions(localizer):
for f_name in list(_localizedFunctions):
f = localizer.localizeFunction(f_name) or _defaultFunctions[f_name]
_localizedFunctions[f_name] = f
if minisix.PY3:
def decode_raw_line(line):
#first, try to decode using utf-8
@ -390,7 +416,7 @@ def matchCase(s1, s2):
L[i] = L[i].upper()
return ''.join(L)
@internationalizeFunction('pluralize')
@internationalizeFunction
def pluralize(s):
"""Returns the plural of s. Put any exceptions to the general English
rule of appending 's' in the plurals dictionary.
@ -413,7 +439,7 @@ def pluralize(s):
else:
return matchCase(s, s+'s')
@internationalizeFunction('depluralize')
@internationalizeFunction
def depluralize(s):
"""Returns the singular of s."""
consonants = 'bcdfghjklmnpqrstvwxz'
@ -467,7 +493,7 @@ def nItems(n, item, between=None):
else:
return format('%s %s %s', n, between, item)
@internationalizeFunction('ordinal')
@internationalizeFunction
def ordinal(i):
"""Returns i + the ordinal indicator for the number.
@ -486,7 +512,7 @@ def ordinal(i):
ord = 'rd'
return '%s%s' % (i, ord)
@internationalizeFunction('be')
@internationalizeFunction
def be(i):
"""Returns the form of the verb 'to be' based on the number i."""
if i == 1:
@ -494,7 +520,7 @@ def be(i):
else:
return 'are'
@internationalizeFunction('has')
@internationalizeFunction
def has(i):
"""Returns the form of the verb 'to have' based on the number i."""
if i == 1: