Creating the internationalization module and internationalize/localize ChannelStats into French

This commit is contained in:
Valentin Lorentz 2010-10-09 11:36:22 +02:00
parent 26a458b9ec
commit 50acd3d8d9
6 changed files with 526 additions and 58 deletions

View File

@ -0,0 +1,155 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-10-06 16:16+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: plugin.py:252
msgid "I couldn't find you in my user database."
msgstr ""
#: plugin.py:265
msgid ""
"%s has sent %n; a total of %n, %n, %n, and %n; %s of those messages %s. %s "
"has joined %n, parted %n, quit %n, kicked someone %n, been kicked %n, "
"changed the topic %n, and changed the mode %n."
msgstr ""
#: plugin.py:272
msgid "character"
msgstr ""
#: plugin.py:273 plugin.py:355
msgid "word"
msgstr ""
#: plugin.py:274 plugin.py:356
msgid "smiley"
msgstr ""
#: plugin.py:275 plugin.py:357
msgid "frown"
msgstr ""
#: plugin.py:277 plugin.py:358
msgid "was an ACTION"
msgstr ""
#: plugin.py:278 plugin.py:359
msgid "were ACTIONs"
msgstr ""
#: plugin.py:280 plugin.py:281 plugin.py:282 plugin.py:283 plugin.py:284
#: plugin.py:285 plugin.py:286
msgid "time"
msgstr ""
#: plugin.py:289
#, python-format
msgid "I have no stats for that %s in %s."
msgstr ""
#: plugin.py:291
msgid ""
"[<channel>] [<name>]\n"
"\n"
" Returns the statistics for <name> on <channel>. <channel> is only\n"
" necessary if the message isn't sent on the channel itself. If "
"<name>\n"
" isn't given, it defaults to the user sending the command.\n"
" "
msgstr ""
#: plugin.py:305
msgid ""
"There's really no reason why you should have underscores or brackets in your "
"mathematical expression. Please remove them."
msgstr ""
#: plugin.py:309
msgid "You can't use lambda in this command."
msgstr ""
#: plugin.py:323
msgid "stat variable"
msgstr ""
#: plugin.py:335
msgid ""
"[<channel>] <stat expression>\n"
"\n"
" Returns the ranking of users according to the given stat "
"expression.\n"
" Valid variables in the stat expression include 'msgs', 'chars',\n"
" 'words', 'smileys', 'frowns', 'actions', 'joins', 'parts', 'quits',\n"
" 'kicks', 'kicked', 'topics', and 'modes'. Any simple mathematical\n"
" expression involving those variables is permitted.\n"
" "
msgstr ""
#: plugin.py:349
msgid ""
"On %s there %h been %i messages, containing %i characters, %n, %n, and %n; "
"%i of those messages %s. There have been %n, %n, %n, %n, %n, and %n. There "
"%b currently %n and the channel has peaked at %n."
msgstr ""
#: plugin.py:360
msgid "join"
msgstr ""
#: plugin.py:361
msgid "part"
msgstr ""
#: plugin.py:362
msgid "quit"
msgstr ""
#: plugin.py:363
msgid "kick"
msgstr ""
#: plugin.py:364
msgid "mode"
msgstr ""
#: plugin.py:364 plugin.py:365
msgid "change"
msgstr ""
#: plugin.py:365
msgid "topic"
msgstr ""
#: plugin.py:367 plugin.py:368
msgid "user"
msgstr ""
#: plugin.py:371
#, python-format
msgid "I've never been on %s."
msgstr ""
#: plugin.py:372
msgid ""
"[<channel>]\n"
"\n"
" Returns the statistics for <channel>. <channel> is only necessary "
"if\n"
" the message isn't sent on the channel itself.\n"
" "
msgstr ""

View File

@ -0,0 +1,167 @@
# French translations for supybot-i package
# Traductions françaises du paquet supybot-i.
# Copyright (C) 2010 THE supybot-i'S COPYRIGHT HOLDER
# This file is distributed under the same license as the supybot-i package.
# ProgVal <progval@gmail.com>, 2010.
#
msgid ""
msgstr ""
"Project-Id-Version: supybot-i 18n\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-10-06 16:16+0200\n"
"PO-Revision-Date: 2010-10-06 16:46+0100\n"
"Last-Translator: Valentin 'ProgVal' Lorentz <progval@gmail.com>\n"
"Language-Team: French\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: plugin.py:252
msgid "I couldn't find you in my user database."
msgstr "Je ne peux vous trouver dans ma base de données."
#: plugin.py:265
msgid "%s has sent %n; a total of %n, %n, %n, and %n; %s of those messages %s. %s has joined %n, parted %n, quit %n, kicked someone %n, been kicked %n, changed the topic %n, and changed the mode %n."
msgstr "%s a envoyé %n ; un total de %n, %n, %n, et %n ; %s de ces messages %s. %s est arrivé %nfois, est parti %nfois, a quitté %nfois, a kické %nfois, a été kické %nfois, a changé le topic %nfois, et a changé les modes %nfois."
#: plugin.py:272
msgid "character"
msgstr "caractère"
#: plugin.py:273
#: plugin.py:355
msgid "word"
msgstr "mot"
#: plugin.py:274
#: plugin.py:356
msgid "smiley"
msgstr "smiley"
#: plugin.py:275
#: plugin.py:357
msgid "frown"
msgstr "sadley"
#: plugin.py:277
#: plugin.py:358
msgid "was an ACTION"
msgstr "était une action"
#: plugin.py:278
#: plugin.py:359
msgid "were ACTIONs"
msgstr "étaient des ACTIONs"
#: plugin.py:280
#: plugin.py:281
#: plugin.py:282
#: plugin.py:283
#: plugin.py:284
#: plugin.py:285
#: plugin.py:286
msgid "time"
msgstr " fois"
#: plugin.py:289
#, python-format
msgid "I have no stats for that %s in %s."
msgstr "Je n'ai pas de statistiques pour %s sur %s."
#: plugin.py:291
msgid ""
"[<channel>] [<name>]\n"
"\n"
" Returns the statistics for <name> on <channel>. <channel> is only\n"
" necessary if the message isn't sent on the channel itself. If <name>\n"
" isn't given, it defaults to the user sending the command.\n"
" "
msgstr ""
"[canal>] [nom]\n"
"\n"
" Retourne les statistiques pour <nom> sur le <canal>. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même. Si <nom> n'est pas donné, il correspond par défaut à l'utilisateur envoyant la commande"
#: plugin.py:305
msgid "There's really no reason why you should have underscores or brackets in your mathematical expression. Please remove them."
msgstr "Il n'y a aucune raison pour que vous ayez des underscores ou des crochets dans vos expressions mathématiques. Veuillez les supprimer."
#: plugin.py:309
msgid "You can't use lambda in this command."
msgstr "Vous ne pouvez utiliser lambda dans cette commande."
#: plugin.py:323
msgid "stat variable"
msgstr "Variable statistique"
#: plugin.py:335
msgid ""
"[<channel>] <stat expression>\n"
"\n"
" Returns the ranking of users according to the given stat expression.\n"
" Valid variables in the stat expression include 'msgs', 'chars',\n"
" 'words', 'smileys', 'frowns', 'actions', 'joins', 'parts', 'quits',\n"
" 'kicks', 'kicked', 'topics', and 'modes'. Any simple mathematical\n"
" expression involving those variables is permitted.\n"
" "
msgstr ""
"[<canal>] <expression de statistiques>\n"
"\n"
"Retourne le rang des utilisateurs en fonction de l'expression de statistiques données. Les variables valides dans l'expression de statistiques sont : 'msgs', 'chars', 'words', 'smileys', 'frowns', 'actions', 'joins', 'parts', 'quits', 'kicks', 'kicked', 'topics', et 'modes'. Toute expression mathématiques simple utilisant ces variables est autorisée."
#: plugin.py:349
msgid "On %s there %h been %i messages, containing %i characters, %n, %n, and %n; %i of those messages %s. There have been %n, %n, %n, %n, %n, and %n. There %b currently %n and the channel has peaked at %n."
msgstr "Sur %s il y a eu%v %i messages, contenant %i caractères, %n, %n, et %n ; %i de ces messages %s. Il y a eu %n, %n, %n, %n, %n, et %n. Il y a%v actuellement %n et le record du canal est de %n."
#: plugin.py:360
msgid "join"
msgstr "arrivée"
#: plugin.py:361
msgid "part"
msgstr "départ"
#: plugin.py:362
msgid "quit"
msgstr "quit"
#: plugin.py:363
msgid "kick"
msgstr "kick"
#: plugin.py:364
msgid "mode"
msgstr "mode"
#: plugin.py:364
#: plugin.py:365
msgid "change"
msgstr "changement"
#: plugin.py:365
msgid "topic"
msgstr "topic"
#: plugin.py:367
#: plugin.py:368
msgid "user"
msgstr "utilisateur"
#: plugin.py:371
#, python-format
msgid "I've never been on %s."
msgstr "Je n'ai jamais été sur %s."
#: plugin.py:372
msgid ""
"[<channel>]\n"
"\n"
" Returns the statistics for <channel>. <channel> is only necessary if\n"
" the message isn't sent on the channel itself.\n"
" "
msgstr ""
"[canal]\n"
"\n"
"Retourne les statistiques pour le <canal>. <canal> n'est nécessaire que si le message n'est pas envoyé sur le canal lui-même."

View File

@ -34,6 +34,7 @@ import types
import supybot.log as log
import supybot.conf as conf
import supybot.i18n as i18n
import supybot.utils as utils
import supybot.world as world
import supybot.ircdb as ircdb
@ -44,6 +45,8 @@ import supybot.plugins as plugins
import supybot.ircutils as ircutils
import supybot.callbacks as callbacks
_ = i18n.PluginInternationalization('ChannelStats')
class ChannelStat(irclib.IrcCommandDispatcher):
_values = ['actions', 'chars', 'frowns', 'joins', 'kicks','modes',
'msgs', 'parts', 'quits', 'smileys', 'topics', 'words', 'users']
@ -239,12 +242,6 @@ class ChannelStats(callbacks.Plugin):
self.db.channels[channel][id].kicked += 1
def stats(self, irc, msg, args, channel, name):
"""[<channel>] [<name>]
Returns the statistics for <name> on <channel>. <channel> is only
necessary if the message isn't sent on the channel itself. If <name>
isn't given, it defaults to the user sending the command.
"""
if name and ircutils.strEqual(name, irc.nick):
id = 0
elif not name:
@ -252,7 +249,7 @@ class ChannelStats(callbacks.Plugin):
id = ircdb.users.getUserId(msg.prefix)
name = ircdb.users.getUser(id).name
except KeyError:
irc.error('I couldn\'t find you in my user database.')
irc.error(_('I couldn\'t find you in my user database.'))
return
elif not ircdb.users.hasUser(name):
try:
@ -265,53 +262,51 @@ class ChannelStats(callbacks.Plugin):
id = ircdb.users.getUserId(name)
try:
stats = self.db.getUserStats(channel, id)
s = format('%s has sent %n; a total of %n, %n, '
'%n, and %n; %s of those messages %s'
s = format(_('%s has sent %n; a total of %n, %n, '
'%n, and %n; %s of those messages %s. '
'%s has joined %n, parted %n, quit %n, '
'kicked someone %n, been kicked %n, '
'changed the topic %n, and changed the '
'mode %n.',
'mode %n.'),
name, (stats.msgs, 'message'),
(stats.chars, 'character'),
(stats.words, 'word'),
(stats.smileys, 'smiley'),
(stats.frowns, 'frown'),
(stats.chars, _('character')),
(stats.words, _('word')),
(stats.smileys, _('smiley')),
(stats.frowns, _('frown')),
stats.actions,
stats.actions == 1 and 'was an ACTION. '
or 'were ACTIONs. ',
stats.actions == 1 and _('was an ACTION')
or _('were ACTIONs'),
name,
(stats.joins, 'time'),
(stats.parts, 'time'),
(stats.quits, 'time'),
(stats.kicks, 'time'),
(stats.kicked, 'time'),
(stats.topics, 'time'),
(stats.modes, 'time'))
(stats.joins, _('time')),
(stats.parts, _('time')),
(stats.quits, _('time')),
(stats.kicks, _('time')),
(stats.kicked, _('time')),
(stats.topics, _('time')),
(stats.modes, _('time')))
irc.reply(s)
except KeyError:
irc.error(format('I have no stats for that %s in %s.',
irc.error(format(_('I have no stats for that %s in %s.'),
name, channel))
stats.__doc__ = _("""[<channel>] [<name>]
Returns the statistics for <name> on <channel>. <channel> is only
necessary if the message isn't sent on the channel itself. If <name>
isn't given, it defaults to the user sending the command.
""")
stats = wrap(stats, ['channeldb', additional('something')])
_env = {'__builtins__': types.ModuleType('__builtins__')}
_env.update(math.__dict__)
def rank(self, irc, msg, args, channel, expr):
"""[<channel>] <stat expression>
Returns the ranking of users according to the given stat expression.
Valid variables in the stat expression include 'msgs', 'chars',
'words', 'smileys', 'frowns', 'actions', 'joins', 'parts', 'quits',
'kicks', 'kicked', 'topics', and 'modes'. Any simple mathematical
expression involving those variables is permitted.
"""
# XXX I could do this the right way, and abstract out a safe eval,
# or I could just copy/paste from the Math plugin.
if expr != expr.translate(utils.str.chars, '_[]'):
irc.error('There\'s really no reason why you should have '
irc.error(_('There\'s really no reason why you should have '
'underscores or brackets in your mathematical '
'expression. Please remove them.', Raise=True)
'expression. Please remove them.'), Raise=True)
if 'lambda' in expr:
irc.error('You can\'t use lambda in this command.', Raise=True)
irc.error(_('You can\'t use lambda in this command.'), Raise=True)
expr = expr.lower()
users = []
for ((c, id), stats) in self.db.items():
@ -325,7 +320,7 @@ class ChannelStats(callbacks.Plugin):
except ZeroDivisionError:
v = float('inf')
except NameError, e:
irc.errorInvalid('stat variable', str(e).split()[1])
irc.errorInvalid(_('stat variable'), str(e).split()[1])
except Exception, e:
irc.error(utils.exnToString(e), Raise=True)
if id == 0:
@ -337,40 +332,48 @@ class ChannelStats(callbacks.Plugin):
s = utils.str.commaAndify(['#%s %s (%.3g)' % (i+1, u, v)
for (i, (v, u)) in enumerate(users)])
irc.reply(s)
rank.__doc__ = _("""[<channel>] <stat expression>
Returns the ranking of users according to the given stat expression.
Valid variables in the stat expression include 'msgs', 'chars',
'words', 'smileys', 'frowns', 'actions', 'joins', 'parts', 'quits',
'kicks', 'kicked', 'topics', and 'modes'. Any simple mathematical
expression involving those variables is permitted.
""")
rank = wrap(rank, ['channeldb', 'text'])
def channelstats(self, irc, msg, args, channel):
"""[<channel>]
Returns the statistics for <channel>. <channel> is only necessary if
the message isn't sent on the channel itself.
"""
try:
stats = self.db.getChannelStats(channel)
curUsers = len(irc.state.channels[channel].users)
s = format('On %s there %h been %i messages, containing %i '
s = format(_('On %s there %h been %i messages, containing %i '
'characters, %n, %n, and %n; '
'%i of those messages %s. There have been '
'%n, %n, %n, %n, %n, and %n. There %b currently %n '
'and the channel has peaked at %n.',
'and the channel has peaked at %n.'),
channel, stats.msgs, stats.msgs, stats.chars,
(stats.words, 'word'),
(stats.smileys, 'smiley'),
(stats.frowns, 'frown'),
stats.actions, stats.actions == 1 and 'was an ACTION'
or 'were ACTIONs',
(stats.joins, 'join'),
(stats.parts, 'part'),
(stats.quits, 'quit'),
(stats.kicks, 'kick'),
(stats.modes, 'mode', 'change'),
(stats.topics, 'topic', 'change'),
(stats.words, _('word')),
(stats.smileys, _('smiley')),
(stats.frowns, _('frown')),
stats.actions, stats.actions == 1 and _('was an ACTION')
or _('were ACTIONs'),
(stats.joins, _('join')),
(stats.parts, _('part')),
(stats.quits, _('quit')),
(stats.kicks, _('kick')),
(stats.modes, _('mode'), _('change')),
(stats.topics, _('topic'), _('change')),
curUsers,
(curUsers, 'user'),
(stats.users, 'user'))
(curUsers, _('user')),
(stats.users, _('user')))
irc.reply(s)
except KeyError:
irc.error(format('I\'ve never been on %s.', channel))
irc.error(format(_('I\'ve never been on %s.'), channel))
channelstats.__doc__ = _("""[<channel>]
Returns the statistics for <channel>. <channel> is only necessary if
the message isn't sent on the channel itself.
""")
channelstats = wrap(channelstats, ['channeldb'])

View File

@ -113,8 +113,14 @@ package_dir = {'supybot': 'src',
'plugins/Time/local/dateutil',
}
package_data = {}
for plugin in plugins:
package_dir['supybot.plugins.' + plugin] = 'plugins/' + plugin
locale_path = 'plugins/' + plugin + '/locale/'
locale_name = 'supybot.plugins.'+plugin
if os.path.exists(locale_path):
package_data.update({locale_name: ['locale/'+s for s in os.listdir(locale_path)]})
version = '0.83.4.1+git'
setup(
@ -151,6 +157,8 @@ setup(
package_dir=package_dir,
package_data=package_data,
scripts=['scripts/supybot',
'scripts/supybot-test',
'scripts/supybot-botchk',

129
src/i18n.py Normal file
View File

@ -0,0 +1,129 @@
###
# 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 internationalisation and localisation managment.
"""
__all__ = ['PluginInternationalization']
import sys
import supybot.conf as conf
WAITING_FOR_MSGID = 1
IN_MSGID = 2
WAITING_FOR_MSGSTR = 3
IN_MSGSTR = 4
MSGID = 'msgid "'
MSGSTR = 'msgstr "'
#registerGlobalValue(supybot, 'language',
# ValidNick('supybot', """Determines the bot's default language.
# Valid values are things like en, fr, de, etc."""))
def get_plugin_dir(plugin_name):
filename = sys.modules[plugin_name].__file__
if filename.endswith("plugin.pyc"):
return filename[0:-len("plugin.pyc")]
elif filename.endswith("plugin.py"):
return filename[0:-len("plugin.py")]
else:
return
i18nClasses = {}
class PluginInternationalization:
"""Internationalization managment for a plugin."""
def __init__(self, name='supybot'):
self.name = name
self.load_locale('toto')
i18nClasses.update({name: self})
def load_locale(self, locale_name):
directory = get_plugin_dir(self.name) + 'locale/'
try:
translation_file = open('%s%s.po' % (directory, locale_name), 'ru')
except IOError: # The translation is unavailable
self.translations = {}
return
step = WAITING_FOR_MSGID
self.translations = {}
for line in translation_file:
line = line[0:-1] # Remove the ending \n
if line.startswith(MSGID):
# Don't check if step is WAITING_FOR_MSGID
untranslated = ''
translated = ''
data = line[len(MSGID):-1]
if len(data) == 0: # Multiline mode
step = IN_MSGID
else:
untranslated += data
step = WAITING_FOR_MSGSTR
elif step is IN_MSGID and line.startswith('"') and \
line.endswith('"'):
untranslated += line[1:-1]
elif step is IN_MSGID and untranslated == '': # Empty MSGID
step = WAITING_FOR_MSGID
elif step is IN_MSGID: # the MSGID is finished
step = WAITING_FOR_MSGSTR
if step is WAITING_FOR_MSGSTR and line.startswith(MSGSTR):
data = line[len(MSGSTR):-1]
if len(data) == 0: # Multiline mode
step = IN_MSGID
else:
self.translations.update({untranslated: data})
step = WAITING_FOR_MSGID
elif step is IN_MSGSTR and line.startswith('"') and \
line.endswith('"'):
untranslated += line[1:-1]
elif step is IN_MSGSTR: # the MSGSTR is finished
step = WAITING_FOR_MSGID
if translated == '':
translated = untranslated
self.translations.update({untranslated: translated})
def __call__(self, untranslated, *args):
if len(args) == 0:
try:
return self.translations[untranslated]
except KeyError:
return untranslated
else:
translation = self(untranslated)
return translation % args

View File

@ -1,6 +1,7 @@
###
# Copyright (c) 2002-2005, Jeremiah Fincher
# Copyright (c) 2008-2009, James Vega
# Copyright (c) 2010, Valentin Lorentz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -362,7 +363,7 @@ def timestamp(t):
t = time.time()
return time.ctime(t)
_formatRe = re.compile('%((?:\d+)?\.\d+f|[bfhiLnpqrstu%])')
_formatRe = re.compile('%((?:\d+)?\.\d+f|[bfhiLnpqrstuv%])')
def format(s, *args, **kwargs):
"""w00t.
@ -379,6 +380,8 @@ def format(s, *args, **kwargs):
n: nItems (takes a 2-tuple of (n, item) or a 3-tuple of (n, between, item))
t: time, formatted (takes an int)
u: url, wrapped in braces (this should be configurable at some point)
v: void : takes one or many arguments, but doesn't display it
(useful for translation)
"""
args = list(args)
args.reverse() # For more efficient popping.
@ -429,6 +432,9 @@ def format(s, *args, **kwargs):
return timestamp(args.pop())
elif char == 'u':
return '<%s>' % args.pop()
elif char == 'v':
args.pop()
return ''
elif char == '%':
return '%'
else: