products/CPSRSS

changeset 145:95d0d9fe7b10

- Taking advantage of the new centralized CPSI18n.tests.translations module.
author madarche
date Wed, 15 Jun 2005 10:33:54 +0000
parents 00647aef6628
children 98d1d9414693
files CHANGES tests/msgfmt.py tests/test_po.py tests/test_translations.py
diffstat 4 files changed, 45 insertions(+), 492 deletions(-) [+]
line diff
     1.1 --- a/CHANGES
     1.2 +++ b/CHANGES
     1.3 @@ -6,7 +6,7 @@
     1.4  -
     1.5  Bug fixes:
     1.6  ~~~~~~~~~~
     1.7 --
     1.8 +- Taking advantage of the new centralized CPSI18n.tests.translations module.
     1.9  New internal features:
    1.10  ~~~~~~~~~~~~~~~~~~~~~~
    1.11  -
     2.1 deleted file mode 100644
     2.2 --- a/tests/msgfmt.py
     2.3 +++ /dev/null
     2.4 @@ -1,187 +0,0 @@
     2.5 -#! /usr/bin/env python
     2.6 -# -*- coding: iso-8859-1 -*-
     2.7 -# Written by Martin v. Loewis <loewis@informatik.hu-berlin.de>
     2.8 -#
     2.9 -# Changed by Christian 'Tiran' Heimes <ch@comlounge.net> for the placeless
    2.10 -# translation service (PTS) of zope
    2.11 -
    2.12 -"""Generate binary message catalog from textual translation description.
    2.13 -
    2.14 -This program converts a textual Uniforum-style message catalog (.po file) into
    2.15 -a binary GNU catalog (.mo file).  This is essentially the same function as the
    2.16 -GNU msgfmt program, however, it is a simpler implementation.
    2.17 -
    2.18 -This file was taken from Python-2.3.2/Tools/i18n and altered in several ways.
    2.19 -Now you can simply use it from another python module:
    2.20 -
    2.21 -  from msgfmt import Msgfmt
    2.22 -  mo = Msgfmt(po).get()
    2.23 -
    2.24 -where po is path to a po file as string, an opened po file ready for reading or
    2.25 -a list of strings (readlines of a po file) and mo is the compiled mo
    2.26 -file as binary string.
    2.27 -
    2.28 -Exceptions:
    2.29 -
    2.30 -  * IOError if the file couldn't be read
    2.31 -
    2.32 -  * msgfmt.PoSyntaxError if the po file has syntax errors
    2.33 -
    2.34 -"""
    2.35 -import sys, os
    2.36 -import struct
    2.37 -import array
    2.38 -from types import FileType, StringType, ListType
    2.39 -from cStringIO import StringIO
    2.40 -
    2.41 -__version__ = "1.1pts"
    2.42 -
    2.43 -
    2.44 -try:
    2.45 -    True
    2.46 -except NameError:
    2.47 -    True=1
    2.48 -    False=0
    2.49 -
    2.50 -class PoSyntaxError(Exception):
    2.51 -    """ Syntax error in a po file """
    2.52 -    def __init__(self, msg):
    2.53 -        self.msg = msg
    2.54 -
    2.55 -    def __str__(self):
    2.56 -        return 'Po file syntax error: %s' % self.msg
    2.57 -
    2.58 -class Msgfmt:
    2.59 -    """ """
    2.60 -    def __init__(self, po, name='unknown'):
    2.61 -        self.po = po
    2.62 -        self.name = name
    2.63 -        self.messages = {}
    2.64 -
    2.65 -    def readPoData(self):
    2.66 -        """ read po data from self.po and store it in self.poLines """
    2.67 -        output = []
    2.68 -        if type(self.po) is FileType:
    2.69 -            self.po.seek(0)
    2.70 -            output = self.po.readlines()
    2.71 -        if type(self.po) is ListType:
    2.72 -            output = self.po
    2.73 -        if type(self.po) is StringType:
    2.74 -            output = open(self.po, 'rb').readlines()
    2.75 -        if not output:
    2.76 -            raise ValueError, "self.po is invalid! %s" % type(self.po)
    2.77 -        return output
    2.78 -
    2.79 -    def add(self, id, str, fuzzy):
    2.80 -        "Add a non-fuzzy translation to the dictionary."
    2.81 -        if not fuzzy and str:
    2.82 -            self.messages[id] = str
    2.83 -
    2.84 -    def generate(self):
    2.85 -        "Return the generated output."
    2.86 -        keys = self.messages.keys()
    2.87 -        # the keys are sorted in the .mo file
    2.88 -        keys.sort()
    2.89 -        offsets = []
    2.90 -        ids = strs = ''
    2.91 -        for id in keys:
    2.92 -            # For each string, we need size and file offset.  Each string is NUL
    2.93 -            # terminated; the NUL does not count into the size.
    2.94 -            offsets.append((len(ids), len(id), len(strs), len(self.messages[id])))
    2.95 -            ids += id + '\0'
    2.96 -            strs += self.messages[id] + '\0'
    2.97 -        output = ''
    2.98 -        # The header is 7 32-bit unsigned integers.  We don't use hash tables, so
    2.99 -        # the keys start right after the index tables.
   2.100 -        # translated string.
   2.101 -        keystart = 7*4+16*len(keys)
   2.102 -        # and the values start after the keys
   2.103 -        valuestart = keystart + len(ids)
   2.104 -        koffsets = []
   2.105 -        voffsets = []
   2.106 -        # The string table first has the list of keys, then the list of values.
   2.107 -        # Each entry has first the size of the string, then the file offset.
   2.108 -        for o1, l1, o2, l2 in offsets:
   2.109 -            koffsets += [l1, o1+keystart]
   2.110 -            voffsets += [l2, o2+valuestart]
   2.111 -        offsets = koffsets + voffsets
   2.112 -        output = struct.pack("Iiiiiii",
   2.113 -                             0x950412deL,       # Magic
   2.114 -                             0,                 # Version
   2.115 -                             len(keys),         # # of entries
   2.116 -                             7*4,               # start of key index
   2.117 -                             7*4+len(keys)*8,   # start of value index
   2.118 -                             0, 0)              # size and offset of hash table
   2.119 -        output += array.array("i", offsets).tostring()
   2.120 -        output += ids
   2.121 -        output += strs
   2.122 -        return output
   2.123 -
   2.124 -
   2.125 -    def get(self):
   2.126 -        """ """
   2.127 -        ID = 1
   2.128 -        STR = 2
   2.129 -
   2.130 -        section = None
   2.131 -        fuzzy = 0
   2.132 -
   2.133 -        lines = self.readPoData()
   2.134 -
   2.135 -        # Parse the catalog
   2.136 -        lno = 0
   2.137 -        #import pdb; pdb.set_trace()
   2.138 -        for l in lines:
   2.139 -            lno += 1
   2.140 -            # If we get a comment line after a msgstr or a line starting with
   2.141 -            # msgid, this is a new entry
   2.142 -            # XXX: l.startswith('msgid') is needed because not all msgid/msgstr
   2.143 -            # pairs in the plone pos have a leading comment
   2.144 -            if (l[0] == '#' or l.startswith('msgid')) and section == STR:
   2.145 -                self.add(msgid, msgstr, fuzzy)
   2.146 -                section = None
   2.147 -                fuzzy = 0
   2.148 -            # Record a fuzzy mark
   2.149 -            if l[:2] == '#,' and l.find('fuzzy'):
   2.150 -                fuzzy = 1
   2.151 -            # Skip comments
   2.152 -            if l[0] == '#':
   2.153 -                continue
   2.154 -            # Now we are in a msgid section, output previous section
   2.155 -            if l.startswith('msgid'):
   2.156 -                section = ID
   2.157 -                l = l[5:]
   2.158 -                msgid = msgstr = ''
   2.159 -            # Now we are in a msgstr section
   2.160 -            elif l.startswith('msgstr'):
   2.161 -                section = STR
   2.162 -                l = l[6:]
   2.163 -            # Skip empty lines
   2.164 -            l = l.strip()
   2.165 -            if not l:
   2.166 -                continue
   2.167 -            # XXX: Does this always follow Python escape semantics?
   2.168 -            # XXX: eval is evil because it could be abused
   2.169 -            try:
   2.170 -                l = eval(l, globals())
   2.171 -            except Exception, msg:
   2.172 -                raise PoSyntaxError('%s (line %d of po file %s): \n%s' % (msg, lno, self.name, l))
   2.173 -            if section == ID:
   2.174 -                msgid += l
   2.175 -            elif section == STR:
   2.176 -                msgstr += l
   2.177 -            else:
   2.178 -                raise PoSyntaxError('error in line %d of po file %s' % (lno, self.name))
   2.179 -
   2.180 -        # Add last entry
   2.181 -        if section == STR:
   2.182 -            self.add(msgid, msgstr, fuzzy)
   2.183 -
   2.184 -        # Compute output
   2.185 -        return self.generate()
   2.186 -
   2.187 -    def getAsFile(self):
   2.188 -        return StringIO(self.get())
   2.189 -
   2.190 -    def __call__(self):
   2.191 -        return self.getAsFile()
     3.1 deleted file mode 100644
     3.2 --- a/tests/test_po.py
     3.3 +++ /dev/null
     3.4 @@ -1,304 +0,0 @@
     3.5 -# (C) Copyright 2005 Nuxeo SARL <http://nuxeo.com>
     3.6 -# (C) Copyright 2005 Unilog <http://unilog.com>
     3.7 -# Authors:
     3.8 -# M.-A. Darche <madarche@nuxeo.com>
     3.9 -# G. de la Rochemace <gdelaroch@unilog.com>
    3.10 -#
    3.11 -# This program is free software; you can redistribute it and/or modify
    3.12 -# it under the terms of the GNU General Public License version 2 as published
    3.13 -# by the Free Software Foundation.
    3.14 -#
    3.15 -# This program is distributed in the hope that it will be useful,
    3.16 -# but WITHOUT ANY WARRANTY; without even the implied warranty of
    3.17 -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    3.18 -# GNU General Public License for more details.
    3.19 -#
    3.20 -# You should have received a copy of the GNU General Public License
    3.21 -# along with this program; if not, write to the Free Software
    3.22 -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
    3.23 -# 02111-1307, USA.
    3.24 -#
    3.25 -# $Id: BaseBox.py 7293 2005-04-03 23:09:11Z janguenot $
    3.26 -
    3.27 -"""Unit tests for .po files
    3.28 -
    3.29 -Adapted from plone-i18n
    3.30 -
    3.31 -References:
    3.32 -http://i18n.kde.org/translation-howto/check-gui.html#check-msgfmt
    3.33 -http://cvs.sourceforge.net/viewcvs.py/plone-i18n/i18n/tests/
    3.34 -"""
    3.35 -
    3.36 -import os, os.path, sys, re
    3.37 -
    3.38 -if __name__ == '__main__':
    3.39 -    execfile(os.path.join(sys.path[0], 'framework.py'))
    3.40 -
    3.41 -from Testing import ZopeTestCase
    3.42 -import unittest
    3.43 -from glob import glob
    3.44 -from gettext import GNUTranslations
    3.45 -from msgfmt import Msgfmt, PoSyntaxError
    3.46 -
    3.47 -_TESTS_PATH = os.path.split(__file__)[0]
    3.48 -if not _TESTS_PATH:
    3.49 -    _TESTS_PATH = '.'
    3.50 -
    3.51 -try:
    3.52 -    import commands
    3.53 -except ImportError:
    3.54 -    if os.name != 'posix':
    3.55 -        raise ImportError("The i18n tests only runs on posix systems, \
    3.56 -                           such as Linux, \
    3.57 -               due to a dependency on Python's commands.getstatusoutput().")
    3.58 -
    3.59 -
    3.60 -def canonizeLang(lang):
    3.61 -    """Return a canonized language name so that language names can easily be
    3.62 -    compared.
    3.63 -    """
    3.64 -    return lang.lower().replace('_', '-')
    3.65 -
    3.66 -
    3.67 -def getLanguageFromPath(path):
    3.68 -    """Check that the same of the .po file corresponds to the contained
    3.69 -    translations.
    3.70 -    """
    3.71 -    # get file
    3.72 -    file = path.split('/')[-1]
    3.73 -    # strip of .po
    3.74 -    file = file[:-3]
    3.75 -    # This code was for CPSSkins which has .po of the form cpsskins-en.po
    3.76 -    #lang = file.split('-')[1:][-1:]
    3.77 -    #return '-'.join(lang)
    3.78 -    return file
    3.79 -
    3.80 -
    3.81 -def getPoPath():
    3.82 -    product_name = __name__.split('.')[0]
    3.83 -    import Products
    3.84 -    product_file = getattr(Products, product_name).__file__
    3.85 -    product_path = os.path.dirname(product_file)
    3.86 -    po_path = os.path.join(product_path, 'i18n')
    3.87 -    return po_path
    3.88 -
    3.89 -
    3.90 -def getPotFiles():
    3.91 -    po_path = getPoPath()
    3.92 -    po_files = [f for f in os.listdir(po_path) if f.endswith('.pot')]
    3.93 -    return po_files
    3.94 -
    3.95 -
    3.96 -def getPoFiles():
    3.97 -    po_path = getPoPath()
    3.98 -    po_files = [f for f in os.listdir(po_path) if f.endswith('.po')]
    3.99 -    return po_files
   3.100 -
   3.101 -
   3.102 -
   3.103 -# DOTALL: Make the "." special character match any character at all, including a
   3.104 -# newline; without this flag, "." will match anything except a newline.
   3.105 -#
   3.106 -# for example:
   3.107 -#
   3.108 -# msgid "button_back"
   3.109 -# msgstr ""
   3.110 -#
   3.111 -# returns 'button_back'
   3.112 -#
   3.113 -MSGID_REGEXP = re.compile('msgid "(.*?)".*?msgstr "', re.DOTALL)
   3.114 -
   3.115 -class TestPOT(ZopeTestCase.ZopeTestCase):
   3.116 -    pot_filename = None
   3.117 -
   3.118 -    def testNoDuplicateMsgId(self):
   3.119 -        """Check that there are no duplicate msgid:s in the pot files"""
   3.120 -
   3.121 -        pot = self.pot_filename
   3.122 -        
   3.123 -        file = open(os.path.join(getPoPath(), pot), 'r')
   3.124 -        file_content = file.read()
   3.125 -        file.close()
   3.126 -
   3.127 -        # Check for duplicate msgids
   3.128 -        matches = re.finditer(MSGID_REGEXP, file_content)
   3.129 -        
   3.130 -        msgids = []
   3.131 -
   3.132 -        for match in matches:
   3.133 -            msgid = match.group(0)
   3.134 -            if msgid in msgids:
   3.135 -                assert 0, "Duplicate msgid:s were found in the file %s :\n\n%s" \
   3.136 -                       % (pot, msgid)
   3.137 -            else:
   3.138 -                msgids.append(msgid)
   3.139 -
   3.140 -
   3.141 -# DOTALL: Make the "." special character match any character at all, including a
   3.142 -# newline; without this flag, "." will match anything except a newline.
   3.143 -#
   3.144 -# #, fuzzy
   3.145 -# msgid ""
   3.146 -# msgstr ""
   3.147 -#
   3.148 -FUZZY_HEADER_ENTRY_REGEXP = re.compile('#, fuzzy\nmsgid ""\nmsgstr ""',
   3.149 -                                       re.DOTALL)
   3.150 -
   3.151 -# IGNORECASE: Perform case-insensitive matching; expressions like [A-Z] will
   3.152 -# match lowercase letters, too. This is not affected by the current locale.
   3.153 -#
   3.154 -# MULTILINE: When specified, the pattern character "^" matches at the beginning
   3.155 -# of the string and at the beginning of each line (immediately following each
   3.156 -# newline); and the pattern character "$" matches at the end of the string and
   3.157 -# at the end of each line (immediately preceding each newline). By default, "^"
   3.158 -# matches only at the beginning of the string, and "$" only at the end of the
   3.159 -# string and immediately before the newline (if any) at the end of the string.
   3.160 -#
   3.161 -# Check the charset:
   3.162 -#
   3.163 -# for example
   3.164 -#
   3.165 -# "Content-Type: text/plain; charset=ISO-8859-15\n"
   3.166 -#
   3.167 -CHARSET_REGEXP = re.compile('^"Content-Type: text/plain; charset=ISO-8859-15',
   3.168 -                            re.MULTILINE | re.IGNORECASE)
   3.169 -
   3.170 -class TestPoFile(ZopeTestCase.ZopeTestCase):
   3.171 -    po_filename = None
   3.172 -
   3.173 -    def testPoFile(self):
   3.174 -        po = self.po_filename
   3.175 -
   3.176 -        po_name = po
   3.177 -        file = open(os.path.join(getPoPath(), po), 'r')
   3.178 -        file_content = file.read()
   3.179 -        file.seek(0)
   3.180 -        try:
   3.181 -            lines = file.readlines()
   3.182 -        except IOError, msg:
   3.183 -            self.fail('Can\'t read po file %s:\n%s' % (po_name, msg))
   3.184 -        file.close()
   3.185 -
   3.186 -        # Checking that the .po file has a non-fuzzy header entry, so that it
   3.187 -        # cannot be deleted by error.
   3.188 -        match_fuzzy = re.findall(FUZZY_HEADER_ENTRY_REGEXP, file_content)
   3.189 -     
   3.190 -        match_charset = re.findall(CHARSET_REGEXP, file_content)
   3.191 -
   3.192 -        if len(match_fuzzy) != 0:
   3.193 -            assert 0, "Fuzzy header entry found in file %s! " \
   3.194 -               "Remove the fuzzy flag on this entry.\n\n" \
   3.195 -               % po_name
   3.196 - 
   3.197 -        if len(match_charset) != 1:
   3.198 -            assert 0, "Invalide charset found in file %s! \n the correct " \
   3.199 -               "line is : 'Content-Type: text/plain; charset=ISO-8859-15'\n\n" \
   3.200 -               % po_name
   3.201 -
   3.202 -        try:
   3.203 -            mo = Msgfmt(lines)
   3.204 -        except PoSyntaxError, msg:
   3.205 -            self.fail('PoSyntaxError: Invalid po data syntax in file %s:\n%s' \
   3.206 -                      % (po_name, msg))
   3.207 -        except SyntaxError, msg:
   3.208 -            self.fail('SyntaxError: Invalid po data syntax in file %s \
   3.209 -                      (Can\'t parse file with eval():\n%s' % (po_name, msg))
   3.210 -        except Exception, msg:
   3.211 -            self.fail('Unknown error while parsing the po file %s:\n%s' \
   3.212 -                      % (po_name, msg))
   3.213 -
   3.214 -        try:
   3.215 -            tro = GNUTranslations(mo.getAsFile())
   3.216 -            #print "tro = %s" % tro
   3.217 -        except UnicodeDecodeError, msg:
   3.218 -            self.fail('UnicodeDecodeError in file %s:\n%s' % (po_name, msg))
   3.219 -        except PoSyntaxError, msg:
   3.220 -            self.fail('PoSyntaxError: Invalid po data syntax in file %s:\n%s' \
   3.221 -                      % (po_name, msg))
   3.222 -
   3.223 -        domain = tro._info.get('domain', None)
   3.224 -        #print "domain = %s" % domain
   3.225 -        self.failUnless(domain, 'Po file %s has no domain!' % po)
   3.226 -
   3.227 -        language_new = tro._info.get('language-code', None) # new way
   3.228 -        #print "language_new = %s" % language_new
   3.229 -        language_old = tro._info.get('language', None) # old way
   3.230 -        #print "language_old = %s" % language_old
   3.231 -        language = language_new or language_old
   3.232 -
   3.233 -        self.failIf(language_old, 'The file %s has the old style language flag \
   3.234 -                                   set to %s. Please remove it!' \
   3.235 -                                  % (po_name, language_old))
   3.236 -
   3.237 -        self.failUnless(language, 'Po file %s has no language!' % po)
   3.238 -
   3.239 -        fileLang = getLanguageFromPath(po)
   3.240 -        #print "getLanguageFromPath = %s" % fileLang
   3.241 -        fileLang = canonizeLang(fileLang)
   3.242 -        #print "canonizeLang = %s" % fileLang
   3.243 -        language = canonizeLang(language)
   3.244 -        #print "language canonizeLang(language) = %s" % language
   3.245 -        self.failUnless(fileLang == language,
   3.246 -            'The file %s has the wrong name or wrong language code. \
   3.247 -             expected: %s, got: %s' % \
   3.248 -             (po_name, language, fileLang))
   3.249 -
   3.250 -        # i18n completeness chart generation mechanism relies on case sensitive
   3.251 -        # Language-Code and Language-Name.
   3.252 -        for meta_info in ['"Language-Code: ',
   3.253 -                          '"Language-Name: ',
   3.254 -                          '"Domain: ',
   3.255 -                          ]:
   3.256 -            cmd = """grep '%s' %s/../i18n/%s""" % (
   3.257 -                meta_info, _TESTS_PATH, po_name)
   3.258 -            #print "cmd = %s" % cmd
   3.259 -            statusoutput = commands.getstatusoutput(cmd)
   3.260 -            #print "status = %s" % statusoutput[0]
   3.261 -            #print "output = %s" % statusoutput[1]
   3.262 -            self.assert_(statusoutput[0] == 0,
   3.263 -                         "Wrong case used for metadata in file %s! "
   3.264 -                         "Check that your metadata is "
   3.265 -                         "Language-Code, Language-Name and Domain.\n\n%s"
   3.266 -                         % (po_name, statusoutput[1]))
   3.267 -
   3.268 -
   3.269 -class TestMsg(ZopeTestCase.ZopeTestCase):
   3.270 -    po_filename = None
   3.271 -    pot_filename = None
   3.272 -
   3.273 -    def checkMsgExists(self,po,template):
   3.274 -        """Check that each existing message is translated and
   3.275 -           that there are no extra messages."""
   3.276 -        cmd = 'LC_ALL=C msgcmp --directory=%s/../i18n %s %s' % (
   3.277 -            TESTS_PATH, po,template)
   3.278 -        status = commands.getstatusoutput(cmd)
   3.279 -        if status[0] != 0:
   3.280 -            return status
   3.281 -        return None
   3.282 -
   3.283 -
   3.284 -tests = []
   3.285 -for pot_filename in getPotFiles():
   3.286 -    class TestOnePOT(TestPOT):
   3.287 -        pot_filename = pot_filename
   3.288 -    tests.append(TestOnePOT)
   3.289 -
   3.290 -for po_filename in getPoFiles():
   3.291 -    class TestOneMsg(TestMsg):
   3.292 -        po_filename = po_filename
   3.293 -    tests.append(TestOneMsg)
   3.294 -
   3.295 -    class TestOnePoFile(TestPoFile):
   3.296 -        po_filename = po_filename
   3.297 -    tests.append(TestOnePoFile)
   3.298 -
   3.299 -
   3.300 -def test_suite():
   3.301 -    suite = unittest.TestSuite()
   3.302 -    for test in tests:
   3.303 -        suite.addTest(unittest.makeSuite(test))
   3.304 -    return suite
   3.305 -
   3.306 -if __name__ == '__main__':
   3.307 -    framework(descriptions=1, verbosity=2)
   3.308 -
     4.1 new file mode 100644
     4.2 --- /dev/null
     4.3 +++ b/tests/test_translations.py
     4.4 @@ -0,0 +1,44 @@
     4.5 +# (C) Copyright 2005 Nuxeo SARL <http://nuxeo.com>
     4.6 +# Authors:
     4.7 +# M.-A. Darche <madarche@nuxeo.com>
     4.8 +#
     4.9 +# This program is free software; you can redistribute it and/or modify
    4.10 +# it under the terms of the GNU General Public License version 2 as published
    4.11 +# by the Free Software Foundation.
    4.12 +#
    4.13 +# This program is distributed in the hope that it will be useful,
    4.14 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    4.15 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    4.16 +# GNU General Public License for more details.
    4.17 +#
    4.18 +# You should have received a copy of the GNU General Public License
    4.19 +# along with this program; if not, write to the Free Software
    4.20 +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
    4.21 +# 02111-1307, USA.
    4.22 +#
    4.23 +# $Id$
    4.24 +"""Unit tests on the well-formedness and quality of .pot and .po files.
    4.25 +"""
    4.26 +from Products.CPSI18n.tests.translations import TranslationsTestCase
    4.27 +from Testing import ZopeTestCase
    4.28 +import unittest
    4.29 +
    4.30 +product_name = __name__.split('.')[0]
    4.31 +
    4.32 +# We need to install this product because the TranslationsTestCase will later on
    4.33 +# find the .pot and .po files from this installed product.
    4.34 +ZopeTestCase.installProduct(product_name)
    4.35 +
    4.36 +
    4.37 +class Test(TranslationsTestCase):
    4.38 +
    4.39 +    def setUp(self):
    4.40 +        self.product_name = product_name
    4.41 +
    4.42 +
    4.43 +def test_suite():
    4.44 +    loader = unittest.TestLoader()
    4.45 +    return loader.loadTestsFromTestCase(Test)
    4.46 +
    4.47 +if __name__ == '__main__':
    4.48 +    unittest.main(defaultTest='test_suite')