mirror of https://github.com/Mikaela/Limnoria.git
Merge branch 'l10n-fr' into testing
This commit is contained in:
commit
27bb53b560
|
@ -0,0 +1,100 @@
|
||||||
|
# -*- encoding: utf8 -*-
|
||||||
|
###
|
||||||
|
# Copyright (c) 2010, Valentin Lorentz
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions, and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of the author of this software nor the name of
|
||||||
|
# contributors to this software may be used to endorse or promote products
|
||||||
|
# derived from this software without specific prior written consent.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
###
|
||||||
|
|
||||||
|
"""
|
||||||
|
Supybot utility functions localization in French.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def pluralize(s):
|
||||||
|
"""Returns the plural of s.
|
||||||
|
"""
|
||||||
|
lowered = s.lower()
|
||||||
|
if lowered.endswith('ou') and \
|
||||||
|
lowered in ['bijou', 'caillou', 'chou', 'genou', 'hibou', 'joujou',
|
||||||
|
'pou']:
|
||||||
|
return s + 'x'
|
||||||
|
elif lowered.endwith('al') and \
|
||||||
|
lowered not in ['bal', 'carnaval', 'chacal', 'festival', 'récital',
|
||||||
|
'régal', 'cal', 'étal', 'aval', 'caracal', 'val', 'choral',
|
||||||
|
'corral', 'galgal', 'gayal']:
|
||||||
|
return s[0:-2] + 'aux'
|
||||||
|
elif lowered.endswith('ail') and \
|
||||||
|
lowered not in ['bail', 'corail', 'émail', 'soupirail', 'travail',
|
||||||
|
'ventail', 'vitrail', 'aspirail', 'fermail']:
|
||||||
|
return s[0:-3] + 'aux'
|
||||||
|
elif lowered.endswith('eau'):
|
||||||
|
return s + 'x'
|
||||||
|
elif lowered == 'pare-feu':
|
||||||
|
return s
|
||||||
|
elif lowered.endwith('eu') and \
|
||||||
|
lowered not in ['bleu', 'pneu', 'émeu', 'enfeu']:
|
||||||
|
# Note: when 'lieu' is a fish, it has a 's' ; else, it has a 'x'
|
||||||
|
return s + 'x'
|
||||||
|
else:
|
||||||
|
return s + 's'
|
||||||
|
|
||||||
|
def depluralize(s):
|
||||||
|
"""Returns the singular of s."""
|
||||||
|
lowered = s.lower()
|
||||||
|
if lowered.endswith('aux') and \
|
||||||
|
lowered in ['baux', 'coraux', 'émaux', 'soupiraux', 'travaux',
|
||||||
|
'ventaux', 'vitraux', 'aspiraux', 'fermaux']:
|
||||||
|
return s[0:-3] + 'ail'
|
||||||
|
elif lowered.endswith('aux'):
|
||||||
|
return s[0:-3] + 'al'
|
||||||
|
else:
|
||||||
|
return s[0:-1]
|
||||||
|
|
||||||
|
def ordinal(i):
|
||||||
|
"""Returns i + the ordinal indicator for the number.
|
||||||
|
|
||||||
|
Example: ordinal(3) => '3ème'
|
||||||
|
"""
|
||||||
|
i = int(i)
|
||||||
|
if i == 1:
|
||||||
|
return '1er'
|
||||||
|
else:
|
||||||
|
return '%sème' % i
|
||||||
|
|
||||||
|
def be(i):
|
||||||
|
"""Returns the form of the verb 'être' based on the number i."""
|
||||||
|
# Note: this function is used only for the third person
|
||||||
|
if i == 1:
|
||||||
|
return 'est'
|
||||||
|
else:
|
||||||
|
return 'sont'
|
||||||
|
|
||||||
|
def has(i):
|
||||||
|
"""Returns the form of the verb 'avoir' based on the number i."""
|
||||||
|
# Note: this function is used only for the third person
|
||||||
|
if i == 1:
|
||||||
|
return 'a'
|
||||||
|
else:
|
||||||
|
return 'ont'
|
|
@ -67,7 +67,7 @@ msgstr ""
|
||||||
|
|
||||||
#: plugin.py:95
|
#: plugin.py:95
|
||||||
msgid "I have spawned %n; %n %b still currently active: %L."
|
msgid "I have spawned %n; %n %b still currently active: %L."
|
||||||
msgstr "J'ai lancé %n ; %n %b sont encore actuellement actifs : %L."
|
msgstr "J'ai lancé %n ; %n %b encore actuellement en vie : %L."
|
||||||
|
|
||||||
#: plugin.py:103
|
#: plugin.py:103
|
||||||
msgid ""
|
msgid ""
|
||||||
|
@ -105,7 +105,7 @@ msgstr "Mes enfants ont pris %.2f secondes du temps utilisateur et %.2f secondes
|
||||||
|
|
||||||
#: plugin.py:138
|
#: plugin.py:138
|
||||||
msgid "I have taken %.2f seconds of user time and %.2f seconds of system time, for a total of %.2f seconds of CPU time. %s"
|
msgid "I have taken %.2f seconds of user time and %.2f seconds of system time, for a total of %.2f seconds of CPU time. %s"
|
||||||
msgstr "J'ai pris %.2f secondes du temps utilisateur et %.2f secondes du temps système, pour un total de %.2f secondes de temps CPU."
|
msgstr "J'ai pris %.2f secondes du temps utilisateur et %.2f secondes du temps système, pour un total de %.2f secondes de temps CPU. %s"
|
||||||
|
|
||||||
#: plugin.py:160
|
#: plugin.py:160
|
||||||
msgid "Unable to run ps command."
|
msgid "Unable to run ps command."
|
||||||
|
|
133
src/i18n.py
133
src/i18n.py
|
@ -31,7 +31,7 @@
|
||||||
Supybot internationalisation and localisation managment.
|
Supybot internationalisation and localisation managment.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__all__ = ['PluginInternationalization']
|
__all__ = ['PluginInternationalization', 'internationalizeDocstring']
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
@ -77,14 +77,29 @@ def getLocalePath(name, localeName, extension):
|
||||||
|
|
||||||
i18nClasses = {}
|
i18nClasses = {}
|
||||||
internationalizedCommands = {}
|
internationalizedCommands = {}
|
||||||
|
internationalizedFunctions = [] # No need to know there name
|
||||||
|
|
||||||
def reloadLocals():
|
def reloadLocals():
|
||||||
for pluginName in i18nClasses:
|
for pluginName in i18nClasses:
|
||||||
i18nClasses[pluginName].loadLocale()
|
i18nClasses[pluginName].loadLocale()
|
||||||
for commandHash in internationalizedCommands:
|
for commandHash in internationalizedCommands:
|
||||||
internationalizeDocstring(internationalizedCommands[commandHash])
|
internationalizeDocstring(internationalizedCommands[commandHash])
|
||||||
|
for function in internationalizedFunctions:
|
||||||
|
function.loadLocale()
|
||||||
|
|
||||||
class PluginInternationalization:
|
|
||||||
|
i18nSupybot = None
|
||||||
|
def PluginInternationalization(name='supybot'):
|
||||||
|
# This is a proxy, that prevent Supybot against having more than one
|
||||||
|
# internationalizer
|
||||||
|
global i18nSupybot
|
||||||
|
if name != 'supybot':
|
||||||
|
return _PluginInternationalization(name)
|
||||||
|
elif i18nSupybot == None:
|
||||||
|
i18nSupybot = _PluginInternationalization('supybot')
|
||||||
|
return i18nSupybot
|
||||||
|
|
||||||
|
class _PluginInternationalization:
|
||||||
"""Internationalization managment for a plugin."""
|
"""Internationalization managment for a plugin."""
|
||||||
def __init__(self, name='supybot'):
|
def __init__(self, name='supybot'):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -96,19 +111,28 @@ class PluginInternationalization:
|
||||||
self.loadLocale()
|
self.loadLocale()
|
||||||
|
|
||||||
def loadLocale(self, localeName=None):
|
def loadLocale(self, localeName=None):
|
||||||
|
"""(Re)loads the locale used by this class."""
|
||||||
if localeName is None and 'conf' in globals():
|
if localeName is None and 'conf' in globals():
|
||||||
localeName = conf.supybot.language()
|
localeName = conf.supybot.language()
|
||||||
elif localeName is None:
|
elif localeName is None:
|
||||||
localeName = 'en'
|
localeName = 'en'
|
||||||
self.currentLocaleName = localeName
|
self.currentLocaleName = localeName
|
||||||
|
|
||||||
|
self._loadL10nCode()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
translationFile = open(getLocalePath(self.name, localeName, 'po'),
|
translationFile = open(getLocalePath(self.name, localeName, 'po'),
|
||||||
'ru') # ru is the mode, not the beginning
|
'ru') # ru is the mode, not the beginning
|
||||||
# of 'russian' ;)
|
# of 'russian' ;)
|
||||||
|
self._parse(translationFile)
|
||||||
except IOError: # The translation is unavailable
|
except IOError: # The translation is unavailable
|
||||||
self.translations = {}
|
self.translations = {}
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _parse(self, translationFile):
|
||||||
|
"""A .po files parser.
|
||||||
|
|
||||||
|
Give it a file object."""
|
||||||
step = WAITING_FOR_MSGID
|
step = WAITING_FOR_MSGID
|
||||||
self.translations = {}
|
self.translations = {}
|
||||||
for line in translationFile:
|
for line in translationFile:
|
||||||
|
@ -154,61 +178,92 @@ class PluginInternationalization:
|
||||||
self._translate(untranslated, translated)
|
self._translate(untranslated, translated)
|
||||||
|
|
||||||
def _translate(self, untranslated, translated):
|
def _translate(self, untranslated, translated):
|
||||||
self.translations.update({self._parse(untranslated):
|
self.translations.update({self._unescape(untranslated):
|
||||||
self._parse(translated)})
|
self._unescape(translated)})
|
||||||
|
|
||||||
def _parse(self, string):
|
def _unescape(self, string):
|
||||||
return str.replace(string, '\\n', '\n') # Replace \\n by \n
|
return str.replace(string, '\\n', '\n')
|
||||||
|
|
||||||
def __call__(self, untranslated, *args):
|
def __call__(self, untranslated):
|
||||||
if not 'conf' in globals():
|
if not 'conf' in globals():
|
||||||
if len(args) == 0:
|
|
||||||
return untranslated
|
return untranslated
|
||||||
else:
|
|
||||||
translation = self(untranslated)
|
|
||||||
return translation % args
|
|
||||||
if self.currentLocaleName != conf.supybot.language():
|
if self.currentLocaleName != conf.supybot.language():
|
||||||
# If the locale has been changed
|
# If the locale has been changed
|
||||||
reloadLocals()
|
reloadLocals()
|
||||||
if len(args) == 0:
|
|
||||||
try:
|
try:
|
||||||
return self.translations[untranslated]
|
return self.translations[untranslated]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return untranslated
|
return untranslated
|
||||||
else:
|
|
||||||
try:
|
|
||||||
return self.translations[untranslated] % args
|
|
||||||
except KeyError:
|
|
||||||
return untranslated % args
|
|
||||||
|
|
||||||
def _getL10nCode(self):
|
def _loadL10nCode(self):
|
||||||
|
if self.name != 'supybot':
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
execfile(self._getL10nCodePath())
|
||||||
|
except IOError: # File doesn't exist
|
||||||
|
pass
|
||||||
|
|
||||||
|
functions = locals()
|
||||||
|
functions.pop('self')
|
||||||
|
self._l10nFunctions = functions
|
||||||
|
# Remove old functions and come back to the native language
|
||||||
|
|
||||||
|
def _getL10nCodePath(self):
|
||||||
|
"""Returns the path to the code localization file.
|
||||||
|
|
||||||
|
It contains functions that needs to by fully (code + strings)
|
||||||
|
localized"""
|
||||||
|
if self.name != 'supybot':
|
||||||
|
return
|
||||||
return getLocalePath('supybot', self.currentLocaleName, 'py')
|
return getLocalePath('supybot', self.currentLocaleName, 'py')
|
||||||
|
|
||||||
def getPluralizers(self, pluralize, depluralize):
|
def localizeFunction(self, name):
|
||||||
# This should be used only by src/utils/str.py
|
"""Returns the localized version of the function.
|
||||||
try:
|
|
||||||
execfile(self._getL10nCode())
|
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
return (pluralize, depluralize)
|
|
||||||
|
|
||||||
def getOrdinal(self, ordinal):
|
Should be used only by the internationalizedFunction class"""
|
||||||
# This should be used only by src/utils/str.py
|
if self.name != 'supybot':
|
||||||
try:
|
return
|
||||||
execfile(self._getL10nCode())
|
if hasattr(self, '_l10nFunctions') and \
|
||||||
except IOError:
|
self._l10nFunctions.has_key(name):
|
||||||
pass
|
return self._l10nFunctions[name]
|
||||||
return ordinal
|
|
||||||
|
|
||||||
def getBeAndHas(self, be, has):
|
def internationalizeFunction(self, name):
|
||||||
# This should be used only by src/utils/str.py
|
"""Decorates functions and internationalize their code.
|
||||||
try:
|
|
||||||
execfile(self._getL10nCode())
|
Only useful for Supybot core functions"""
|
||||||
except IOError:
|
if self.name != 'supybot':
|
||||||
pass
|
return
|
||||||
return (be, has)
|
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 locale/LOCALE.py"""
|
||||||
|
def __init__(self, internationalizer, name, function):
|
||||||
|
self._internationalizer = internationalizer
|
||||||
|
self._name = name
|
||||||
|
self.__call__ = function
|
||||||
|
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 internationalizeDocstring(obj):
|
def internationalizeDocstring(obj):
|
||||||
|
"""Decorates functions and internationalize their docstring.
|
||||||
|
|
||||||
|
Only useful for commands (commands' docstring is displayed on IRC)"""
|
||||||
if sys.modules[obj.__module__].__dict__.has_key('_'):
|
if sys.modules[obj.__module__].__dict__.has_key('_'):
|
||||||
internationalizedCommands.update({hash(obj): obj})
|
internationalizedCommands.update({hash(obj): obj})
|
||||||
obj.__doc__=sys.modules[obj.__module__]._.__call__(obj.__doc__)
|
obj.__doc__=sys.modules[obj.__module__]._.__call__(obj.__doc__)
|
||||||
|
|
|
@ -43,7 +43,7 @@ from iter import all, any
|
||||||
from structures import TwoWayDictionary
|
from structures import TwoWayDictionary
|
||||||
|
|
||||||
from supybot.i18n import PluginInternationalization
|
from supybot.i18n import PluginInternationalization
|
||||||
_ = PluginInternationalization()
|
internationalizeFunction=PluginInternationalization().internationalizeFunction
|
||||||
|
|
||||||
curry = new.instancemethod
|
curry = new.instancemethod
|
||||||
chars = string.maketrans('', '')
|
chars = string.maketrans('', '')
|
||||||
|
@ -256,6 +256,7 @@ def matchCase(s1, s2):
|
||||||
L[i] = L[i].upper()
|
L[i] = L[i].upper()
|
||||||
return ''.join(L)
|
return ''.join(L)
|
||||||
|
|
||||||
|
@internationalizeFunction('pluralize')
|
||||||
def pluralize(s):
|
def pluralize(s):
|
||||||
"""Returns the plural of s. Put any exceptions to the general English
|
"""Returns the plural of s. Put any exceptions to the general English
|
||||||
rule of appending 's' in the plurals dictionary.
|
rule of appending 's' in the plurals dictionary.
|
||||||
|
@ -278,6 +279,7 @@ def pluralize(s):
|
||||||
else:
|
else:
|
||||||
return matchCase(s, s+'s')
|
return matchCase(s, s+'s')
|
||||||
|
|
||||||
|
@internationalizeFunction('depluralize')
|
||||||
def depluralize(s):
|
def depluralize(s):
|
||||||
"""Returns the singular of s."""
|
"""Returns the singular of s."""
|
||||||
_depluralizeRegex = re.compile('[%s]ies' % consonants)
|
_depluralizeRegex = re.compile('[%s]ies' % consonants)
|
||||||
|
@ -294,8 +296,6 @@ def depluralize(s):
|
||||||
else:
|
else:
|
||||||
return s # Don't know what to do.
|
return s # Don't know what to do.
|
||||||
|
|
||||||
pluralize, depluralize = _.getPluralizers(pluralize, depluralize)
|
|
||||||
|
|
||||||
def nItems(n, item, between=None):
|
def nItems(n, item, between=None):
|
||||||
"""Works like this:
|
"""Works like this:
|
||||||
|
|
||||||
|
@ -332,6 +332,7 @@ def nItems(n, item, between=None):
|
||||||
else:
|
else:
|
||||||
return format('%s %s %s', n, between, item)
|
return format('%s %s %s', n, between, item)
|
||||||
|
|
||||||
|
@internationalizeFunction('ordinal')
|
||||||
def ordinal(i):
|
def ordinal(i):
|
||||||
"""Returns i + the ordinal indicator for the number.
|
"""Returns i + the ordinal indicator for the number.
|
||||||
|
|
||||||
|
@ -350,8 +351,7 @@ def ordinal(i):
|
||||||
ord = 'rd'
|
ord = 'rd'
|
||||||
return '%s%s' % (i, ord)
|
return '%s%s' % (i, ord)
|
||||||
|
|
||||||
ordinal = _.getOrdinal(ordinal)
|
@internationalizeFunction('be')
|
||||||
|
|
||||||
def be(i):
|
def be(i):
|
||||||
"""Returns the form of the verb 'to be' based on the number i."""
|
"""Returns the form of the verb 'to be' based on the number i."""
|
||||||
if i == 1:
|
if i == 1:
|
||||||
|
@ -359,6 +359,7 @@ def be(i):
|
||||||
else:
|
else:
|
||||||
return 'are'
|
return 'are'
|
||||||
|
|
||||||
|
@internationalizeFunction('has')
|
||||||
def has(i):
|
def has(i):
|
||||||
"""Returns the form of the verb 'to have' based on the number i."""
|
"""Returns the form of the verb 'to have' based on the number i."""
|
||||||
if i == 1:
|
if i == 1:
|
||||||
|
@ -366,8 +367,6 @@ def has(i):
|
||||||
else:
|
else:
|
||||||
return 'have'
|
return 'have'
|
||||||
|
|
||||||
be, has = _.getBeAndHas(be, has)
|
|
||||||
|
|
||||||
def toBool(s):
|
def toBool(s):
|
||||||
s = s.strip().lower()
|
s = s.strip().lower()
|
||||||
if s in ('true', 'on', 'enable', 'enabled', '1'):
|
if s in ('true', 'on', 'enable', 'enabled', '1'):
|
||||||
|
|
Loading…
Reference in New Issue