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 #: 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."

View File

@ -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:
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: try:
execfile(self._getL10nCode()) return self.translations[untranslated]
except IOError: except KeyError:
return untranslated
def _loadL10nCode(self):
if self.name != 'supybot':
return
try:
execfile(self._getL10nCodePath())
except IOError: # File doesn't exist
pass 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): It contains functions that needs to by fully (code + strings)
# This should be used only by src/utils/str.py localized"""
try: if self.name != 'supybot':
execfile(self._getL10nCode()) return
except IOError: return getLocalePath('supybot', self.currentLocaleName, 'py')
pass
return ordinal def localizeFunction(self, name):
"""Returns the localized version of the function.
def getBeAndHas(self, be, has): 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 (be, has)
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): 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__)

View File

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