Merge branch 'l10n-fr' into testing

This commit is contained in:
Valentin Lorentz 2010-10-30 21:41:44 +02:00
commit 27bb53b560
4 changed files with 208 additions and 54 deletions

100
locale/fr.py Normal file
View File

@ -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'

View File

@ -67,7 +67,7 @@ msgstr ""
#: plugin.py:95
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
msgid ""
@ -105,7 +105,7 @@ msgstr "Mes enfants ont pris %.2f secondes du temps utilisateur et %.2f secondes
#: 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"
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
msgid "Unable to run ps command."

View File

@ -31,7 +31,7 @@
Supybot internationalisation and localisation managment.
"""
__all__ = ['PluginInternationalization']
__all__ = ['PluginInternationalization', 'internationalizeDocstring']
import re
import sys
@ -77,14 +77,29 @@ def getLocalePath(name, localeName, extension):
i18nClasses = {}
internationalizedCommands = {}
internationalizedFunctions = [] # No need to know there name
def reloadLocals():
for pluginName in i18nClasses:
i18nClasses[pluginName].loadLocale()
for commandHash in internationalizedCommands:
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."""
def __init__(self, name='supybot'):
self.name = name
@ -96,19 +111,28 @@ class PluginInternationalization:
self.loadLocale()
def loadLocale(self, localeName=None):
"""(Re)loads the locale used by this class."""
if localeName is None and 'conf' in globals():
localeName = conf.supybot.language()
elif localeName is None:
localeName = 'en'
self.currentLocaleName = localeName
self._loadL10nCode()
try:
translationFile = open(getLocalePath(self.name, localeName, 'po'),
'ru') # ru is the mode, not the beginning
# of 'russian' ;)
self._parse(translationFile)
except IOError: # The translation is unavailable
self.translations = {}
return
def _parse(self, translationFile):
"""A .po files parser.
Give it a file object."""
step = WAITING_FOR_MSGID
self.translations = {}
for line in translationFile:
@ -154,61 +178,92 @@ class PluginInternationalization:
self._translate(untranslated, translated)
def _translate(self, untranslated, translated):
self.translations.update({self._parse(untranslated):
self._parse(translated)})
self.translations.update({self._unescape(untranslated):
self._unescape(translated)})
def _parse(self, string):
return str.replace(string, '\\n', '\n') # Replace \\n by \n
def _unescape(self, string):
return str.replace(string, '\\n', '\n')
def __call__(self, untranslated, *args):
def __call__(self, untranslated):
if not 'conf' in globals():
if len(args) == 0:
return untranslated
else:
translation = self(untranslated)
return translation % args
return untranslated
if self.currentLocaleName != conf.supybot.language():
# If the locale has been changed
reloadLocals()
if len(args) == 0:
try:
return self.translations[untranslated]
except KeyError:
return untranslated
else:
try:
return self.translations[untranslated] % args
except KeyError:
return untranslated % args
def _getL10nCode(self):
return getLocalePath('supybot', self.currentLocaleName, 'py')
def getPluralizers(self, pluralize, depluralize):
# This should be used only by src/utils/str.py
try:
execfile(self._getL10nCode())
except IOError:
return self.translations[untranslated]
except KeyError:
return untranslated
def _loadL10nCode(self):
if self.name != 'supybot':
return
try:
execfile(self._getL10nCodePath())
except IOError: # File doesn't exist
pass
return (pluralize, depluralize)
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.
def getOrdinal(self, ordinal):
# This should be used only by src/utils/str.py
try:
execfile(self._getL10nCode())
except IOError:
pass
return ordinal
It contains functions that needs to by fully (code + strings)
localized"""
if self.name != 'supybot':
return
return getLocalePath('supybot', self.currentLocaleName, 'py')
def localizeFunction(self, name):
"""Returns the localized version of the function.
def getBeAndHas(self, be, has):
# This should be used only by src/utils/str.py
try:
execfile(self._getL10nCode())
except IOError:
pass
return (be, has)
Should be used only by the internationalizedFunction class"""
if self.name != 'supybot':
return
if hasattr(self, '_l10nFunctions') and \
self._l10nFunctions.has_key(name):
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 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):
"""Decorates functions and internationalize their docstring.
Only useful for commands (commands' docstring is displayed on IRC)"""
if sys.modules[obj.__module__].__dict__.has_key('_'):
internationalizedCommands.update({hash(obj): obj})
obj.__doc__=sys.modules[obj.__module__]._.__call__(obj.__doc__)

View File

@ -43,7 +43,7 @@ from iter import all, any
from structures import TwoWayDictionary
from supybot.i18n import PluginInternationalization
_ = PluginInternationalization()
internationalizeFunction=PluginInternationalization().internationalizeFunction
curry = new.instancemethod
chars = string.maketrans('', '')
@ -256,6 +256,7 @@ def matchCase(s1, s2):
L[i] = L[i].upper()
return ''.join(L)
@internationalizeFunction('pluralize')
def pluralize(s):
"""Returns the plural of s. Put any exceptions to the general English
rule of appending 's' in the plurals dictionary.
@ -278,6 +279,7 @@ def pluralize(s):
else:
return matchCase(s, s+'s')
@internationalizeFunction('depluralize')
def depluralize(s):
"""Returns the singular of s."""
_depluralizeRegex = re.compile('[%s]ies' % consonants)
@ -294,8 +296,6 @@ def depluralize(s):
else:
return s # Don't know what to do.
pluralize, depluralize = _.getPluralizers(pluralize, depluralize)
def nItems(n, item, between=None):
"""Works like this:
@ -332,6 +332,7 @@ def nItems(n, item, between=None):
else:
return format('%s %s %s', n, between, item)
@internationalizeFunction('ordinal')
def ordinal(i):
"""Returns i + the ordinal indicator for the number.
@ -350,8 +351,7 @@ def ordinal(i):
ord = 'rd'
return '%s%s' % (i, ord)
ordinal = _.getOrdinal(ordinal)
@internationalizeFunction('be')
def be(i):
"""Returns the form of the verb 'to be' based on the number i."""
if i == 1:
@ -359,6 +359,7 @@ def be(i):
else:
return 'are'
@internationalizeFunction('has')
def has(i):
"""Returns the form of the verb 'to have' based on the number i."""
if i == 1:
@ -366,8 +367,6 @@ def has(i):
else:
return 'have'
be, has = _.getBeAndHas(be, has)
def toBool(s):
s = s.strip().lower()
if s in ('true', 'on', 'enable', 'enabled', '1'):