File indexing completed on 2024-04-14 03:59:11

0001 # pylint: disable=too-many-lines
0002 # -*- coding: utf-8 -*-
0003 
0004 """
0005 Copyright (C) 2008-2016 Wolfgang Rohdewald <wolfgang@rohdewald.de>
0006 
0007 SPDX-License-Identifier: GPL-2.0
0008 
0009 
0010 
0011 Here we define replacement classes for the case that we have no
0012 python interface to KDE.
0013 
0014 """
0015 
0016 
0017 import os
0018 import sys
0019 from locale import _parse_localename, getdefaultlocale, setlocale, LC_ALL
0020 
0021 
0022 # pylint: disable=wrong-import-order
0023 
0024 # here come the replacements:
0025 
0026 # pylint: disable=wildcard-import,unused-wildcard-import
0027 from qt import *
0028 
0029 from common import Internal, Debug
0030 from util import uniqueList
0031 
0032 import gettext
0033 
0034 
0035 try:
0036     from kdepaths import LOCALEPATH
0037 except ImportError:
0038     LOCALEPATH = None
0039 
0040 __all__ = ['i18n', 'i18nc', 'qi18nc', 'i18nE', 'i18ncE', 'KDETranslator', 'MLocale']
0041 
0042 
0043 ENGLISHDICT = {}
0044 
0045 def __insertArgs(translatedTemplate, *args):
0046     """
0047     put format arguments into the translated template.
0048     KDE semantics markup is removed.
0049 
0050     @param translatedTemplate: The translated string template.
0051     @type translatedTemplate: C{str}
0052     @param args: The format arguments
0053     @type args: A list or tuple of C{str}
0054 
0055     @return: The formatted translated text.
0056     @rtype: C{str}
0057     """
0058 
0059     if '\004' in translatedTemplate:
0060         translatedTemplate = translatedTemplate.split('\004')[1]
0061     result = translatedTemplate
0062     if '%' in result:
0063         for idx in range(len(args)):
0064             result = result.replace('%%%d' % (idx + 1), '{%d}' % idx)
0065         result = result.format(*args)
0066     for ignore in ['numid', 'filename', 'interface']:
0067         result = result.replace('<%s>' % ignore, '')
0068         result = result.replace('</%s>' % ignore, '')
0069     return result
0070 
0071 
0072 def i18n(englishIn, *args):
0073     """
0074     Translate. Since this is a 1:1 replacement for the
0075     corresponding KDE function, it accepts only C{str}.
0076 
0077     @param englishIn: The english template.
0078     @type englishIn: C{str}
0079     @return: The translated text, args included.
0080     @rtype: C{str}
0081     """
0082     if not Debug.neutral and englishIn:
0083         _ = MLocale.gettext(englishIn)
0084     else:
0085         _ = englishIn
0086     if not args:
0087         ENGLISHDICT[_] = englishIn
0088     result = __insertArgs(_, *args)
0089     return result
0090 
0091 
0092 def i18nc(context, englishIn, *args):
0093     """
0094     Translate. Since this is a 1:1 replacement for the
0095     corresponding KDE function, it accepts only C{str}.
0096 
0097     @param context: The context of this string.
0098     @type context: C{str}
0099     @param englishIn: The english template.
0100     @type englishIn: C{str}
0101     @return: The translated text, args included.
0102     @rtype: C{str}
0103     """
0104     # The \004 trick is taken from kdecore/localization/gettext.h,
0105     # definition of pgettext_aux"""
0106     if Debug.neutral:
0107         return __insertArgs(englishIn, *args)
0108     withContext = '\004'.join([context, englishIn])
0109     _ = MLocale.gettext(withContext)
0110     if _ == withContext:
0111         # try again without context
0112         _ = MLocale.gettext(englishIn)
0113     if not args:
0114         ENGLISHDICT[_] = englishIn
0115     return __insertArgs(_, *args)
0116 
0117 def qi18nc(context, englishIn, *args):
0118     """This uses the Qt translation files"""
0119     if Internal.app is None:
0120         _ = englishIn
0121     else:
0122         _ = Internal.app.translate(context, englishIn)
0123     return __insertArgs(_, *args)
0124 
0125 def i18nE(englishText):
0126     """use this if you want to get the english text right now but still have the string translated"""
0127     return englishText
0128 
0129 
0130 def i18ncE(unusedContext, englishText):
0131     """use this if you want to get the english text right now but still have the string translated"""
0132     return englishText
0133 
0134 def english(i18nstring):
0135     """translate back from local language"""
0136     return ENGLISHDICT.get(i18nstring, i18nstring)
0137 
0138 
0139 class KDETranslator(QTranslator):
0140 
0141     """we also want Qt-only strings translated. Make Qt call this
0142     translator for its own strings. Use this with qi18nc()"""
0143 
0144     def __init__(self, parent):
0145         QTranslator.__init__(self, parent)
0146 
0147     def translate(self, context, text, disambiguation, numerus=-1):
0148         """context should be the class name defined by qt5 or kf5"""
0149         if Debug.neutral:
0150             return text
0151         result = QTranslator.translate(self, context, text, disambiguation, numerus)
0152         if result:
0153             return result
0154         if not MLocale.currentLanguages():
0155             # when starting kajongg.py, qt5 loads translators for the system default
0156             # language. I do not know how to avoid that. And I cannot delete
0157             # translators I do not own. So if we want no translation, just return text.
0158             # But we still need to install our own QTranslator overriding the ones
0159             # mentioned above. It will never find a translation, so we come here.
0160             assert Internal.app.translators == [self]
0161         return text
0162 
0163 
0164 
0165 class MLocale:
0166     """xxxx"""
0167 
0168     __cached_availableLanguages = None
0169 
0170     translation = None
0171 
0172     @classmethod
0173     def gettext(cls, txt):
0174         """with lazy installation of languages"""
0175         if cls.translation is None:
0176             cls.installTranslations()
0177         return cls.translation.gettext(txt)
0178 
0179     @classmethod
0180     def installTranslations(cls):
0181         """install translations"""
0182         if os.name == 'nt':
0183             # on Linux, QCoreApplication initializes locale but not on Windows.
0184             # This is actually documented for QCoreApplication
0185             setlocale(LC_ALL, '')
0186         languages = cls.currentLanguages()
0187         cls.translation = gettext.NullTranslations()
0188         if Debug.i18n:
0189             Internal.logger.debug('Trying to install translations for %s', ','.join(languages))
0190         for language in languages:
0191             for context in ('kajongg', 'libkmahjongg5', 'kxmlgui5', 'kconfigwidgets5', 'libc'):
0192                 directories = cls.localeDirectories()
0193                 for resourceDir in directories:
0194                     try:
0195                         cls.translation.add_fallback(gettext.translation(
0196                             context, resourceDir, languages=[language]))
0197                         if Debug.i18n:
0198                             Internal.logger.debug(
0199                                 'Found %s translation for %s in %s', language, context, resourceDir)
0200                         break
0201                     except IOError as _:
0202                         if Debug.i18n:
0203                             Internal.logger.debug(str(_))
0204         cls.translation.install()
0205 
0206     @staticmethod
0207     def localeDirectories():
0208         """hard coded paths to i18n directories, all are searched"""
0209         candidates = (
0210             'share/locale', '/usr/local/share/locale', '/usr/share/locale',
0211             os.path.join(os.path.dirname(sys.argv[0]), 'share/locale'))
0212         result = [x for x in candidates if os.path.exists(x)]
0213         if not result and Debug.i18n:
0214             Internal.logger.debug('no locale path found. We have:%s', os.listdir('.'))
0215 
0216         if LOCALEPATH and os.path.exists(LOCALEPATH):
0217             result.insert(0, LOCALEPATH)
0218         return result
0219 
0220     @classmethod
0221     def currentLanguages(cls):
0222         """the currently used languages, primary first"""
0223         languages = Internal.kajonggrc.group('Locale').readEntry('Language')
0224         if not languages:
0225             return list()
0226         languages = languages.split(':')
0227         if 'en_US' in languages:
0228             languages.remove('en_US')
0229         return languages
0230 
0231     @staticmethod
0232     def extendRegionLanguages(languages):
0233         """for de_DE, return de_DE, de"""
0234         for lang in languages:
0235             if lang is not None:
0236                 yield lang
0237                 if '_' in lang:
0238                     yield lang.split('_')[0]
0239 
0240     @staticmethod
0241     def get_localenames():
0242         """parse environment variables"""
0243         result = []
0244         defaultlocale = getdefaultlocale()[0]
0245         if defaultlocale:
0246             result.append(defaultlocale)
0247         for variable in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
0248             try:
0249                 localename = os.environ[variable]
0250                 if localename is None:
0251                     raise Exception('{} is None'.format(variable))
0252             except KeyError:
0253                 continue
0254             else:
0255                 if variable == 'LANGUAGE':
0256                     result.extend(localename.split(':'))
0257                 else:
0258                     result.append(localename)
0259         if Debug.i18n:
0260             Internal.logger.debug('get_localenames: %s', format(','.join(result)))
0261         return result
0262 
0263     @classmethod
0264     def availableLanguages(cls):
0265         """see python lib, getdefaultlocale (which only returns the first one)"""
0266         if cls.__cached_availableLanguages:
0267             return cls.__cached_availableLanguages
0268         localenames = cls.get_localenames()
0269         languages = list()
0270         for _ in localenames:
0271             if _ is None:
0272                 continue
0273             _ = _parse_localename(_)
0274             if _[0]:
0275                 languages.append(_[0])
0276         if Debug.i18n:
0277             Internal.logger.debug('languages from locale: %s', ','.join(languages) if languages else None)
0278             Internal.logger.debug('looking for translations in %s', ','.join(cls.localeDirectories()))
0279         installed_languages = set()
0280         for resourceDir in cls.localeDirectories():
0281             installed_languages |= set(os.listdir(resourceDir))
0282         for sysLanguage in sorted(installed_languages):
0283             if cls.__isLanguageInstalledForKajongg(sysLanguage):
0284                 languages.append(sysLanguage)
0285 
0286         if languages:
0287             languages = uniqueList(cls.extendRegionLanguages(languages))
0288             languages = [x for x in languages if cls.isLanguageInstalled(x)]
0289         if 'en_US' not in languages:
0290             languages.extend(['en_US', 'en'])
0291         if Debug.i18n:
0292             Internal.logger.debug('languages available: %s', ':'.join(languages) if languages else None)
0293         cls.__cached_availableLanguages = ':'.join(languages)
0294         return cls.__cached_availableLanguages
0295 
0296     @classmethod
0297     def availableLanguages_(cls):
0298         """like availableLanguages but if xx_yy exists, exclude xx"""
0299         languages = set(cls.availableLanguages().split(':'))
0300         for _ in list(languages):
0301             if '_' in _ and _[:2] in languages:
0302                 languages.remove(_[:2])
0303         return ':'.join(sorted(languages))
0304 
0305     @classmethod
0306     def isLanguageInstalled(cls, lang):  # TODO: should be is Available
0307         """is any translation available for lang?"""
0308         if lang == 'en_US':
0309             return True
0310         for directory in cls.localeDirectories():
0311             if os.path.exists(os.path.join(directory, lang)):
0312                 return True
0313         return False
0314 
0315     @classmethod
0316     def __isLanguageInstalledForKajongg(cls, lang):
0317         """see kdelibs, KCatalog::catalogLocaleDir"""
0318         for directory in cls.localeDirectories():
0319             _ = os.path.join(directory, lang, 'LC_MESSAGES', 'kajongg.mo')
0320             if os.path.exists(_):
0321                 if Debug.i18n:
0322                     Internal.logger.debug('language %s installed in %s', lang, _)
0323                 return True
0324         return False