File indexing completed on 2024-04-28 07:51:09
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