From ce4a32fe52ef09d8f5ff1dd22c001110902b60a2 Mon Sep 17 00:00:00 2001 From: toma Date: Wed, 25 Nov 2009 17:56:58 +0000 Subject: Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features. BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdelibs@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- kdecore/klocale.cpp | 2448 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2448 insertions(+) create mode 100644 kdecore/klocale.cpp (limited to 'kdecore/klocale.cpp') diff --git a/kdecore/klocale.cpp b/kdecore/klocale.cpp new file mode 100644 index 000000000..1ca1f035e --- /dev/null +++ b/kdecore/klocale.cpp @@ -0,0 +1,2448 @@ +// -*- c-basic-offset: 2 -*- +/* This file is part of the KDE libraries + Copyright (c) 1997,2001 Stephan Kulow + Copyright (c) 1999 Preston Brown + Copyright (c) 1999-2002 Hans Petter Bieker + Copyright (c) 2002 Lukas Tinkl + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include // getenv + +#include +#include +#include +#include +#include +#include + +#include "kcatalogue.h" +#include "kglobal.h" +#include "kstandarddirs.h" +#include "ksimpleconfig.h" +#include "kinstance.h" +#include "kconfig.h" +#include "kdebug.h" +#include "kcalendarsystem.h" +#include "kcalendarsystemfactory.h" +#include "klocale.h" + +#ifdef Q_WS_WIN +#include +#endif + +static const char * const SYSTEM_MESSAGES = "kdelibs"; + +static const char *maincatalogue = 0; + +class KLocalePrivate +{ +public: + int weekStartDay; + bool nounDeclension; + bool dateMonthNamePossessive; + QStringList languageList; + QStringList catalogNames; // list of all catalogs (regardless of language) + QValueList catalogues; // list of all loaded catalogs, contains one instance per catalog name and language + QString encoding; + QTextCodec * codecForEncoding; + KConfig * config; + bool formatInited; + int /*QPrinter::PageSize*/ pageSize; + KLocale::MeasureSystem measureSystem; + QStringList langTwoAlpha; + KConfig *languages; + + QString calendarType; + KCalendarSystem * calendar; + bool utf8FileEncoding; + QString appName; +#ifdef Q_WS_WIN + char win32SystemEncoding[3+7]; //"cp " + lang ID +#endif +}; + +static KLocale *this_klocale = 0; + +KLocale::KLocale( const QString & catalog, KConfig * config ) +{ + d = new KLocalePrivate; + d->config = config; + d->languages = 0; + d->calendar = 0; + d->formatInited = false; + + initEncoding(0); + initFileNameEncoding(0); + + KConfig *cfg = d->config; + this_klocale = this; + if (!cfg) cfg = KGlobal::instance()->config(); + this_klocale = 0; + Q_ASSERT( cfg ); + + d->appName = catalog; + initLanguageList( cfg, config == 0); + initMainCatalogues(catalog); +} + +QString KLocale::_initLanguage(KConfigBase *config) +{ + if (this_klocale) + { + // ### HPB Why this cast?? + this_klocale->initLanguageList((KConfig *) config, true); + // todo: adapt current catalog list: remove unused languages, insert main catalogs, if not already found + return this_klocale->language(); + } + return QString::null; +} + +void KLocale::initMainCatalogues(const QString & catalog) +{ + // Use the first non-null string. + QString mainCatalogue = catalog; + if (maincatalogue) + mainCatalogue = QString::fromLatin1(maincatalogue); + + if (mainCatalogue.isEmpty()) { + kdDebug(173) << "KLocale instance created called without valid " + << "catalog! Give an argument or call setMainCatalogue " + << "before init" << endl; + } + else { + // do not use insertCatalogue here, that would already trigger updateCatalogs + d->catalogNames.append( mainCatalogue ); // application catalog + d->catalogNames.append( SYSTEM_MESSAGES ); // always include kdelibs.mo + d->catalogNames.append( "kio" ); // always include kio.mo + updateCatalogues(); // evaluate this for all languages + } +} + +void KLocale::initLanguageList(KConfig * config, bool useEnv) +{ + KConfigGroupSaver saver(config, "Locale"); + + m_country = config->readEntry( "Country" ); + if ( m_country.isEmpty() ) + m_country = defaultCountry(); + + // Reset the list and add the new languages + QStringList languageList; + if ( useEnv ) + languageList += QStringList::split + (':', QFile::decodeName( ::getenv("KDE_LANG") )); + + languageList += config->readListEntry("Language", ':'); + + // same order as setlocale use + if ( useEnv ) + { + // HPB: Only run splitLocale on the environment variables.. + QStringList langs; + + langs << QFile::decodeName( ::getenv("LC_ALL") ); + langs << QFile::decodeName( ::getenv("LC_MESSAGES") ); + langs << QFile::decodeName( ::getenv("LANG") ); + + for ( QStringList::Iterator it = langs.begin(); + it != langs.end(); + ++it ) + { + QString ln, ct, chrset; + splitLocale(*it, ln, ct, chrset); + + if (!ct.isEmpty()) { + langs.insert(it, ln + '_' + ct); + if (!chrset.isEmpty()) + langs.insert(it, ln + '_' + ct + '.' + chrset); + } + + langs.insert(it, ln); + } + + languageList += langs; + } + + // now we have a language list -- let's use the first OK language + setLanguage( languageList ); +} + +void KLocale::initPluralTypes() +{ + for ( QValueList::Iterator it = d->catalogues.begin(); + it != d->catalogues.end(); + ++it ) + { + QString language = (*it).language(); + int pt = pluralType( language ); + (*it).setPluralType( pt ); + } +} + + +int KLocale::pluralType( const QString & language ) +{ + for ( QValueList::ConstIterator it = d->catalogues.begin(); + it != d->catalogues.end(); + ++it ) + { + if ( ((*it).name() == SYSTEM_MESSAGES ) && ((*it).language() == language )) { + return pluralType( *it ); + } + } + // kdelibs.mo does not seem to exist for this language + return -1; +} + +int KLocale::pluralType( const KCatalogue& catalog ) +{ + const char* pluralFormString = + I18N_NOOP("_: Dear translator, please do not translate this string " + "in any form, but pick the _right_ value out of " + "NoPlural/TwoForms/French... If not sure what to do mail " + "thd@kde.org and coolo@kde.org, they will tell you. " + "Better leave that out if unsure, the programs will " + "crash!!\nDefinition of PluralForm - to be set by the " + "translator of kdelibs.po"); + QString pf (catalog.translate( pluralFormString)); + if ( pf.isEmpty() ) { + return -1; + } + else if ( pf == "NoPlural" ) + return 0; + else if ( pf == "TwoForms" ) + return 1; + else if ( pf == "French" ) + return 2; + else if ( pf == "OneTwoRest" ) + return 3; + else if ( pf == "Russian" ) + return 4; + else if ( pf == "Polish" ) + return 5; + else if ( pf == "Slovenian" ) + return 6; + else if ( pf == "Lithuanian" ) + return 7; + else if ( pf == "Czech" ) + return 8; + else if ( pf == "Slovak" ) + return 9; + else if ( pf == "Maltese" ) + return 10; + else if ( pf == "Arabic" ) + return 11; + else if ( pf == "Balcan" ) + return 12; + else if ( pf == "Macedonian" ) + return 13; + else if ( pf == "Gaeilge" ) + return 14; + else { + kdWarning(173) << "Definition of PluralForm is none of " + << "NoPlural/" + << "TwoForms/" + << "French/" + << "OneTwoRest/" + << "Russian/" + << "Polish/" + << "Slovenian/" + << "Lithuanian/" + << "Czech/" + << "Slovak/" + << "Arabic/" + << "Balcan/" + << "Macedonian/" + << "Gaeilge/" + << "Maltese: " << pf << endl; + exit(1); + } +} + +void KLocale::doFormatInit() const +{ + if ( d->formatInited ) return; + + KLocale * that = const_cast(this); + that->initFormat(); + + d->formatInited = true; +} + +void KLocale::initFormat() +{ + KConfig *config = d->config; + if (!config) config = KGlobal::instance()->config(); + Q_ASSERT( config ); + + kdDebug(173) << "KLocale::initFormat" << endl; + + // make sure the config files are read using the correct locale + // ### Why not add a KConfigBase::setLocale( const KLocale * )? + // ### Then we could remove this hack + KLocale *lsave = KGlobal::_locale; + KGlobal::_locale = this; + + KConfigGroupSaver saver(config, "Locale"); + + KSimpleConfig entry(locate("locale", + QString::fromLatin1("l10n/%1/entry.desktop") + .arg(m_country)), true); + entry.setGroup("KCM Locale"); + + // Numeric +#define readConfigEntry(key, default, save) \ + save = entry.readEntry(key, QString::fromLatin1(default)); \ + save = config->readEntry(key, save); + +#define readConfigNumEntry(key, default, save, type) \ + save = (type)entry.readNumEntry(key, default); \ + save = (type)config->readNumEntry(key, save); + +#define readConfigBoolEntry(key, default, save) \ + save = entry.readBoolEntry(key, default); \ + save = config->readBoolEntry(key, save); + + readConfigEntry("DecimalSymbol", ".", m_decimalSymbol); + readConfigEntry("ThousandsSeparator", ",", m_thousandsSeparator); + m_thousandsSeparator.replace( QString::fromLatin1("$0"), QString::null ); + //kdDebug(173) << "m_thousandsSeparator=" << m_thousandsSeparator << endl; + + readConfigEntry("PositiveSign", "", m_positiveSign); + readConfigEntry("NegativeSign", "-", m_negativeSign); + + // Monetary + readConfigEntry("CurrencySymbol", "$", m_currencySymbol); + readConfigEntry("MonetaryDecimalSymbol", ".", m_monetaryDecimalSymbol); + readConfigEntry("MonetaryThousandsSeparator", ",", + m_monetaryThousandsSeparator); + m_monetaryThousandsSeparator.replace(QString::fromLatin1("$0"), QString::null); + + readConfigNumEntry("FracDigits", 2, m_fracDigits, int); + readConfigBoolEntry("PositivePrefixCurrencySymbol", true, + m_positivePrefixCurrencySymbol); + readConfigBoolEntry("NegativePrefixCurrencySymbol", true, + m_negativePrefixCurrencySymbol); + readConfigNumEntry("PositiveMonetarySignPosition", (int)BeforeQuantityMoney, + m_positiveMonetarySignPosition, SignPosition); + readConfigNumEntry("NegativeMonetarySignPosition", (int)ParensAround, + m_negativeMonetarySignPosition, SignPosition); + + + // Date and time + readConfigEntry("TimeFormat", "%H:%M:%S", m_timeFormat); + readConfigEntry("DateFormat", "%A %d %B %Y", m_dateFormat); + readConfigEntry("DateFormatShort", "%Y-%m-%d", m_dateFormatShort); + readConfigNumEntry("WeekStartDay", 1, d->weekStartDay, int); + + // other + readConfigNumEntry("PageSize", (int)QPrinter::A4, d->pageSize, int); + readConfigNumEntry("MeasureSystem", (int)Metric, d->measureSystem, + MeasureSystem); + readConfigEntry("CalendarSystem", "gregorian", d->calendarType); + delete d->calendar; + d->calendar = 0; // ### HPB Is this the correct place? + + //Grammatical + //Precedence here is l10n / i18n / config file + KSimpleConfig language(locate("locale", + QString::fromLatin1("%1/entry.desktop") + .arg(m_language)), true); + language.setGroup("KCM Locale"); +#define read3ConfigBoolEntry(key, default, save) \ + save = entry.readBoolEntry(key, default); \ + save = language.readBoolEntry(key, save); \ + save = config->readBoolEntry(key, save); + + read3ConfigBoolEntry("NounDeclension", false, d->nounDeclension); + read3ConfigBoolEntry("DateMonthNamePossessive", false, + d->dateMonthNamePossessive); + + // end of hack + KGlobal::_locale = lsave; +} + +bool KLocale::setCountry(const QString & country) +{ + // Check if the file exists too?? + if ( country.isEmpty() ) + return false; + + m_country = country; + + d->formatInited = false; + + return true; +} + +QString KLocale::catalogueFileName(const QString & language, + const KCatalogue & catalog) +{ + QString path = QString::fromLatin1("%1/LC_MESSAGES/%2.mo") + .arg( language ) + .arg( catalog.name() ); + + return locate( "locale", path ); +} + +bool KLocale::setLanguage(const QString & language) +{ + if ( d->languageList.contains( language ) ) { + d->languageList.remove( language ); + } + d->languageList.prepend( language ); // let us consider this language to be the most important one + + m_language = language; // remember main language for shortcut evaluation + + // important when called from the outside and harmless when called before populating the + // catalog name list + updateCatalogues(); + + d->formatInited = false; + + return true; // Maybe the mo-files for this language are empty, but in principle we can speak all languages +} + +bool KLocale::setLanguage(const QStringList & languages) +{ + QStringList languageList( languages ); + // This list might contain + // 1) some empty strings that we have to eliminate + // 2) duplicate entries like in de:fr:de, where we have to keep the first occurrance of a language in order + // to preserve the order of precenence of the user => iterate backwards + // 3) languages into which the application is not translated. For those languages we should not even load kdelibs.mo or kio.po. + // these langugage have to be dropped. Otherwise we get strange side effects, e.g. with Hebrew: + // the right/left switch for languages that write from + // right to left (like Hebrew or Arabic) is set in kdelibs.mo. If you only have kdelibs.mo + // but nothing from appname.mo, you get a mostly English app with layout from right to left. + // That was considered to be a bug by the Hebrew translators. + for( QStringList::Iterator it = languageList.fromLast(); + it != languageList.begin(); --it ) + { + // kdDebug() << "checking " << (*it) << endl; + bool bIsTranslated = isApplicationTranslatedInto( *it ); + if ( languageList.contains(*it) > 1 || (*it).isEmpty() || (!bIsTranslated) ) { + // kdDebug() << "removing " << (*it) << endl; + it = languageList.remove( it ); + } + } + // now this has left the first element of the list unchecked. + // The question why this is the case is left as an exercise for the reader... + // Besides the list might have been empty all the way, so check that too. + if ( languageList.begin() != languageList.end() ) { + QStringList::Iterator it = languageList.begin(); // now pointing to the first element + // kdDebug() << "checking " << (*it) << endl; + if( (*it).isEmpty() || !(isApplicationTranslatedInto( *it )) ) { + // kdDebug() << "removing " << (*it) << endl; + languageList.remove( it ); // that's what the iterator was for... + } + } + + if ( languageList.isEmpty() ) { + // user picked no language, so we assume he/she speaks English. + languageList.append( defaultLanguage() ); + } + m_language = languageList.first(); // keep this for shortcut evaluations + + d->languageList = languageList; // keep this new list of languages to use + d->langTwoAlpha.clear(); // Flush cache + + // important when called from the outside and harmless when called before populating the + // catalog name list + updateCatalogues(); + + return true; // we found something. Maybe it's only English, but we found something +} + +bool KLocale::isApplicationTranslatedInto( const QString & language) +{ + if ( language.isEmpty() ) { + return false; + } + + if ( language == defaultLanguage() ) { + // en_us is always "installed" + return true; + } + + QString appName = d->appName; + if (maincatalogue) { + appName = QString::fromLatin1(maincatalogue); + } + // sorry, catalogueFileName requires catalog object,k which we do not have here + // path finding was supposed to be moved completely to KCatalogue. The interface cannot + // be changed that far during deep freeze. So in order to fix the bug now, we have + // duplicated code for file path evaluation. Cleanup will follow later. We could have e.g. + // a static method in KCataloge that can translate between these file names. + // a stat + QString sFileName = QString::fromLatin1("%1/LC_MESSAGES/%2.mo") + .arg( language ) + .arg( appName ); + // kdDebug() << "isApplicationTranslatedInto: filename " << sFileName << endl; + + QString sAbsFileName = locate( "locale", sFileName ); + // kdDebug() << "isApplicationTranslatedInto: absname " << sAbsFileName << endl; + return ! sAbsFileName.isEmpty(); +} + +void KLocale::splitLocale(const QString & aStr, + QString & language, + QString & country, + QString & chrset) +{ + QString str = aStr; + + // just in case, there is another language appended + int f = str.find(':'); + if (f >= 0) + str.truncate(f); + + country = QString::null; + chrset = QString::null; + language = QString::null; + + f = str.find('.'); + if (f >= 0) + { + chrset = str.mid(f + 1); + str.truncate(f); + } + + f = str.find('_'); + if (f >= 0) + { + country = str.mid(f + 1); + str.truncate(f); + } + + language = str; +} + +QString KLocale::language() const +{ + return m_language; +} + +QString KLocale::country() const +{ + return m_country; +} + +QString KLocale::monthName(int i, bool shortName) const +{ + if ( shortName ) + switch ( i ) + { + case 1: return translate("January", "Jan"); + case 2: return translate("February", "Feb"); + case 3: return translate("March", "Mar"); + case 4: return translate("April", "Apr"); + case 5: return translate("May short", "May"); + case 6: return translate("June", "Jun"); + case 7: return translate("July", "Jul"); + case 8: return translate("August", "Aug"); + case 9: return translate("September", "Sep"); + case 10: return translate("October", "Oct"); + case 11: return translate("November", "Nov"); + case 12: return translate("December", "Dec"); + } + else + switch (i) + { + case 1: return translate("January"); + case 2: return translate("February"); + case 3: return translate("March"); + case 4: return translate("April"); + case 5: return translate("May long", "May"); + case 6: return translate("June"); + case 7: return translate("July"); + case 8: return translate("August"); + case 9: return translate("September"); + case 10: return translate("October"); + case 11: return translate("November"); + case 12: return translate("December"); + } + + return QString::null; +} + +QString KLocale::monthNamePossessive(int i, bool shortName) const +{ + if ( shortName ) + switch ( i ) + { + case 1: return translate("of January", "of Jan"); + case 2: return translate("of February", "of Feb"); + case 3: return translate("of March", "of Mar"); + case 4: return translate("of April", "of Apr"); + case 5: return translate("of May short", "of May"); + case 6: return translate("of June", "of Jun"); + case 7: return translate("of July", "of Jul"); + case 8: return translate("of August", "of Aug"); + case 9: return translate("of September", "of Sep"); + case 10: return translate("of October", "of Oct"); + case 11: return translate("of November", "of Nov"); + case 12: return translate("of December", "of Dec"); + } + else + switch (i) + { + case 1: return translate("of January"); + case 2: return translate("of February"); + case 3: return translate("of March"); + case 4: return translate("of April"); + case 5: return translate("of May long", "of May"); + case 6: return translate("of June"); + case 7: return translate("of July"); + case 8: return translate("of August"); + case 9: return translate("of September"); + case 10: return translate("of October"); + case 11: return translate("of November"); + case 12: return translate("of December"); + } + + return QString::null; +} + +QString KLocale::weekDayName (int i, bool shortName) const +{ + return calendar()->weekDayName(i, shortName); +} + +void KLocale::insertCatalogue( const QString & catalog ) +{ + if ( !d->catalogNames.contains( catalog) ) { + d->catalogNames.append( catalog ); + } + updateCatalogues( ); // evaluate the changed list and generate the neccessary KCatalog objects +} + +void KLocale::updateCatalogues( ) +{ + // some changes have occured. Maybe we have learned or forgotten some languages. + // Maybe the language precedence has changed. + // Maybe we have learned or forgotten some catalog names. + // Now examine the list of KCatalogue objects and change it according to the new circumstances. + + // this could be optimized: try to reuse old KCatalog objects, but remember that the order of + // catalogs might have changed: e.g. in this fashion + // 1) move all catalogs into a temporary list + // 2) iterate over all languages and catalog names + // 3.1) pick the catalog from the saved list, if it already exists + // 3.2) else create a new catalog. + // but we will do this later. + + for ( QValueList::Iterator it = d->catalogues.begin(); + it != d->catalogues.end(); ) + { + it = d->catalogues.remove(it); + } + + // now iterate over all languages and all wanted catalog names and append or create them in the right order + // the sequence must be e.g. nds/appname nds/kdelibs nds/kio de/appname de/kdelibs de/kio etc. + // and not nds/appname de/appname nds/kdelibs de/kdelibs etc. Otherwise we would be in trouble with a language + // sequende nds,en_US, de. In this case en_US must hide everything below in the language list. + for ( QStringList::ConstIterator itLangs = d->languageList.begin(); + itLangs != d->languageList.end(); ++itLangs) + { + for ( QStringList::ConstIterator itNames = d->catalogNames.begin(); + itNames != d->catalogNames.end(); ++itNames) + { + KCatalogue cat( *itNames, *itLangs ); // create Catalog for this name and this language + d->catalogues.append( cat ); + } + } + initPluralTypes(); // evaluate the plural type for all languages and remember this in each KCatalogue +} + + + + +void KLocale::removeCatalogue(const QString &catalog) +{ + if ( d->catalogNames.contains( catalog )) { + d->catalogNames.remove( catalog ); + if (KGlobal::_instance) + updateCatalogues(); // walk through the KCatalogue instances and weed out everything we no longer need + } +} + +void KLocale::setActiveCatalogue(const QString &catalog) +{ + if ( d->catalogNames.contains( catalog ) ) { + d->catalogNames.remove( catalog ); + d->catalogNames.prepend( catalog ); + updateCatalogues(); // walk through the KCatalogue instances and adapt to the new order + } +} + +KLocale::~KLocale() +{ + delete d->calendar; + delete d->languages; + delete d; + d = 0L; +} + +QString KLocale::translate_priv(const char *msgid, + const char *fallback, + const char **translated, + int* pluralType ) const +{ + if ( pluralType) { + *pluralType = -1; // unless we find something more precise + } + if (!msgid || !msgid[0]) + { + kdWarning() << "KLocale: trying to look up \"\" in catalog. " + << "Fix the program" << endl; + return QString::null; + } + + if ( useDefaultLanguage() ) { // shortcut evaluation if en_US is main language: do not consult the catalogs + return QString::fromUtf8( fallback ); + } + + for ( QValueList::ConstIterator it = d->catalogues.begin(); + it != d->catalogues.end(); + ++it ) + { + // shortcut evaluation: once we have arrived at en_US (default language) we cannot consult + // the catalog as it will not have an assiciated mo-file. For this default language we can + // immediately pick the fallback string. + if ( (*it).language() == defaultLanguage() ) { + return QString::fromUtf8( fallback ); + } + + const char * text = (*it).translate( msgid ); + + if ( text ) + { + // we found it + if (translated) { + *translated = text; + } + if ( pluralType) { + *pluralType = (*it).pluralType(); // remember the plural type information from the catalog that was used + } + return QString::fromUtf8( text ); + } + } + + // Always use UTF-8 if the string was not found + return QString::fromUtf8( fallback ); +} + +QString KLocale::translate(const char* msgid) const +{ + return translate_priv(msgid, msgid); +} + +QString KLocale::translate( const char *index, const char *fallback) const +{ + if (!index || !index[0] || !fallback || !fallback[0]) + { + kdDebug(173) << "KLocale: trying to look up \"\" in catalog. " + << "Fix the program" << endl; + return QString::null; + } + + if ( useDefaultLanguage() ) + return QString::fromUtf8( fallback ); + + char *newstring = new char[strlen(index) + strlen(fallback) + 5]; + sprintf(newstring, "_: %s\n%s", index, fallback); + // as copying QString is very fast, it looks slower as it is ;/ + QString r = translate_priv(newstring, fallback); + delete [] newstring; + + return r; +} + +static QString put_n_in(const QString &orig, unsigned long n) +{ + QString ret = orig; + int index = ret.find("%n"); + if (index == -1) + return ret; + ret.replace(index, 2, QString::number(n)); + return ret; +} + +#define EXPECT_LENGTH(x) \ + if (forms.count() != x) { \ + kdError() << "translation of \"" << singular << "\" doesn't contain " << x << " different plural forms as expected\n"; \ + return QString( "BROKEN TRANSLATION %1" ).arg( singular ); } + +QString KLocale::translate( const char *singular, const char *plural, + unsigned long n ) const +{ + if (!singular || !singular[0] || !plural || !plural[0]) + { + kdWarning() << "KLocale: trying to look up \"\" in catalog. " + << "Fix the program" << endl; + return QString::null; + } + + char *newstring = new char[strlen(singular) + strlen(plural) + 6]; + sprintf(newstring, "_n: %s\n%s", singular, plural); + // as copying QString is very fast, it looks slower as it is ;/ + int pluralType = -1; + QString r = translate_priv(newstring, 0, 0, &pluralType); + delete [] newstring; + + if ( r.isEmpty() || useDefaultLanguage() || pluralType == -1) { + if ( n == 1 ) { + return put_n_in( QString::fromUtf8( singular ), n ); + } else { + QString tmp = QString::fromUtf8( plural ); +#ifndef NDEBUG + if (tmp.find("%n") == -1) { + kdDebug() << "the message for i18n should contain a '%n'! " << plural << endl; + } +#endif + return put_n_in( tmp, n ); + } + } + + QStringList forms = QStringList::split( "\n", r, false ); + switch ( pluralType ) { + case 0: // NoPlural + EXPECT_LENGTH( 1 ); + return put_n_in( forms[0], n); + case 1: // TwoForms + EXPECT_LENGTH( 2 ); + if ( n == 1 ) + return put_n_in( forms[0], n); + else + return put_n_in( forms[1], n); + case 2: // French + EXPECT_LENGTH( 2 ); + if ( n == 1 || n == 0 ) + return put_n_in( forms[0], n); + else + return put_n_in( forms[1], n); + case 3: // OneTwoRest + EXPECT_LENGTH( 3 ); + if ( n == 1 ) + return put_n_in( forms[0], n); + else if ( n == 2 ) + return put_n_in( forms[1], n); + else + return put_n_in( forms[2], n); + case 4: // Russian, corrected by mok + EXPECT_LENGTH( 3 ); + if ( n%10 == 1 && n%100 != 11) + return put_n_in( forms[0], n); // odin fail + else if (( n%10 >= 2 && n%10 <=4 ) && (n%100<10 || n%100>20)) + return put_n_in( forms[1], n); // dva faila + else + return put_n_in( forms[2], n); // desyat' failov + case 5: // Polish + EXPECT_LENGTH( 3 ); + if ( n == 1 ) + return put_n_in( forms[0], n); + else if ( n%10 >= 2 && n%10 <=4 && (n%100<10 || n%100>=20) ) + return put_n_in( forms[1], n); + else + return put_n_in( forms[2], n); + case 6: // Slovenian + EXPECT_LENGTH( 4 ); + if ( n%100 == 1 ) + return put_n_in( forms[1], n); // ena datoteka + else if ( n%100 == 2 ) + return put_n_in( forms[2], n); // dve datoteki + else if ( n%100 == 3 || n%100 == 4 ) + return put_n_in( forms[3], n); // tri datoteke + else + return put_n_in( forms[0], n); // sto datotek + case 7: // Lithuanian + EXPECT_LENGTH( 3 ); + if ( n%10 == 0 || (n%100>=11 && n%100<=19) ) + return put_n_in( forms[2], n); + else if ( n%10 == 1 ) + return put_n_in( forms[0], n); + else + return put_n_in( forms[1], n); + case 8: // Czech - use modern form which is equivalent to Slovak + case 9: // Slovak + EXPECT_LENGTH( 3 ); + if ( n == 1 ) + return put_n_in( forms[0], n); + else if (( n >= 2 ) && ( n <= 4 )) + return put_n_in( forms[1], n); + else + return put_n_in( forms[2], n); + case 10: // Maltese + EXPECT_LENGTH( 4 ); + if ( n == 1 ) + return put_n_in( forms[0], n ); + else if ( ( n == 0 ) || ( n%100 > 0 && n%100 <= 10 ) ) + return put_n_in( forms[1], n ); + else if ( n%100 > 10 && n%100 < 20 ) + return put_n_in( forms[2], n ); + else + return put_n_in( forms[3], n ); + case 11: // Arabic + EXPECT_LENGTH( 4 ); + if (n == 1) + return put_n_in(forms[0], n); + else if (n == 2) + return put_n_in(forms[1], n); + else if ( n < 11) + return put_n_in(forms[2], n); + else + return put_n_in(forms[3], n); + case 12: // Balcan + EXPECT_LENGTH( 3 ); + if (n != 11 && n % 10 == 1) + return put_n_in(forms[0], n); + else if (n / 10 != 1 && n % 10 >= 2 && n % 10 <= 4) + return put_n_in(forms[1], n); + else + return put_n_in(forms[2], n); + case 13: // Macedonian + EXPECT_LENGTH(3); + if (n % 10 == 1) + return put_n_in(forms[0], n); + else if (n % 10 == 2) + return put_n_in(forms[1], n); + else + return put_n_in(forms[2], n); + case 14: // Gaeilge + EXPECT_LENGTH(5); + if (n == 1) // "ceann amhain" + return put_n_in(forms[0], n); + else if (n == 2) // "dha cheann" + return put_n_in(forms[1], n); + else if (n < 7) // "%n cinn" + return put_n_in(forms[2], n); + else if (n < 11) // "%n gcinn" + return put_n_in(forms[3], n); + else // "%n ceann" + return put_n_in(forms[4], n); + } + kdFatal() << "The function should have been returned in another way\n"; + + return QString::null; +} + +QString KLocale::translateQt( const char *context, const char *source, + const char *message) const +{ + if (!source || !source[0]) { + kdWarning() << "KLocale: trying to look up \"\" in catalog. " + << "Fix the program" << endl; + return QString::null; + } + + if ( useDefaultLanguage() ) { + return QString::null; + } + + char *newstring = 0; + const char *translation = 0; + QString r; + + if ( message && message[0]) { + char *newstring = new char[strlen(source) + strlen(message) + 5]; + sprintf(newstring, "_: %s\n%s", source, message); + const char *translation = 0; + // as copying QString is very fast, it looks slower as it is ;/ + r = translate_priv(newstring, source, &translation); + delete [] newstring; + if (translation) + return r; + } + + if ( context && context[0] && message && message[0]) { + newstring = new char[strlen(context) + strlen(message) + 5]; + sprintf(newstring, "_: %s\n%s", context, message); + // as copying QString is very fast, it looks slower as it is ;/ + r = translate_priv(newstring, source, &translation); + delete [] newstring; + if (translation) + return r; + } + + r = translate_priv(source, source, &translation); + if (translation) + return r; + return QString::null; +} + +bool KLocale::nounDeclension() const +{ + doFormatInit(); + return d->nounDeclension; +} + +bool KLocale::dateMonthNamePossessive() const +{ + doFormatInit(); + return d->dateMonthNamePossessive; +} + +int KLocale::weekStartDay() const +{ + doFormatInit(); + return d->weekStartDay; +} + +bool KLocale::weekStartsMonday() const //deprecated +{ + doFormatInit(); + return (d->weekStartDay==1); +} + +QString KLocale::decimalSymbol() const +{ + doFormatInit(); + return m_decimalSymbol; +} + +QString KLocale::thousandsSeparator() const +{ + doFormatInit(); + return m_thousandsSeparator; +} + +QString KLocale::currencySymbol() const +{ + doFormatInit(); + return m_currencySymbol; +} + +QString KLocale::monetaryDecimalSymbol() const +{ + doFormatInit(); + return m_monetaryDecimalSymbol; +} + +QString KLocale::monetaryThousandsSeparator() const +{ + doFormatInit(); + return m_monetaryThousandsSeparator; +} + +QString KLocale::positiveSign() const +{ + doFormatInit(); + return m_positiveSign; +} + +QString KLocale::negativeSign() const +{ + doFormatInit(); + return m_negativeSign; +} + +int KLocale::fracDigits() const +{ + doFormatInit(); + return m_fracDigits; +} + +bool KLocale::positivePrefixCurrencySymbol() const +{ + doFormatInit(); + return m_positivePrefixCurrencySymbol; +} + +bool KLocale::negativePrefixCurrencySymbol() const +{ + doFormatInit(); + return m_negativePrefixCurrencySymbol; +} + +KLocale::SignPosition KLocale::positiveMonetarySignPosition() const +{ + doFormatInit(); + return m_positiveMonetarySignPosition; +} + +KLocale::SignPosition KLocale::negativeMonetarySignPosition() const +{ + doFormatInit(); + return m_negativeMonetarySignPosition; +} + +static inline void put_it_in( QChar *buffer, uint& index, const QString &s ) +{ + for ( uint l = 0; l < s.length(); l++ ) + buffer[index++] = s.at( l ); +} + +static inline void put_it_in( QChar *buffer, uint& index, int number ) +{ + buffer[index++] = number / 10 + '0'; + buffer[index++] = number % 10 + '0'; +} + +// insert (thousands)-"separator"s into the non-fractional part of str +static void _insertSeparator(QString &str, const QString &separator, + const QString &decimalSymbol) +{ + // leave fractional part untouched + QString mainPart = str.section(decimalSymbol, 0, 0); + QString fracPart = str.section(decimalSymbol, 1, 1, + QString::SectionIncludeLeadingSep); + + for (int pos = mainPart.length() - 3; pos > 0; pos -= 3) + mainPart.insert(pos, separator); + + str = mainPart + fracPart; +} + +QString KLocale::formatMoney(double num, + const QString & symbol, + int precision) const +{ + // some defaults + QString currency = symbol.isNull() + ? currencySymbol() + : symbol; + if (precision < 0) precision = fracDigits(); + + // the number itself + bool neg = num < 0; + QString res = QString::number(neg?-num:num, 'f', precision); + + // Replace dot with locale decimal separator + res.replace(QChar('.'), monetaryDecimalSymbol()); + + // Insert the thousand separators + _insertSeparator(res, monetaryThousandsSeparator(), monetaryDecimalSymbol()); + + // set some variables we need later + int signpos = neg + ? negativeMonetarySignPosition() + : positiveMonetarySignPosition(); + QString sign = neg + ? negativeSign() + : positiveSign(); + + switch (signpos) + { + case ParensAround: + res.prepend('('); + res.append (')'); + break; + case BeforeQuantityMoney: + res.prepend(sign); + break; + case AfterQuantityMoney: + res.append(sign); + break; + case BeforeMoney: + currency.prepend(sign); + break; + case AfterMoney: + currency.append(sign); + break; + } + + if (neg?negativePrefixCurrencySymbol(): + positivePrefixCurrencySymbol()) + { + res.prepend(' '); + res.prepend(currency); + } else { + res.append (' '); + res.append (currency); + } + + return res; +} + +QString KLocale::formatMoney(const QString &numStr) const +{ + return formatMoney(numStr.toDouble()); +} + +QString KLocale::formatNumber(double num, int precision) const +{ + if (precision == -1) precision = 2; + // no need to round since QString::number does this for us + return formatNumber(QString::number(num, 'f', precision), false, 0); +} + +QString KLocale::formatLong(long num) const +{ + return formatNumber((double)num, 0); +} + +QString KLocale::formatNumber(const QString &numStr) const +{ + return formatNumber(numStr, true, 2); +} + +// increase the digit at 'position' by one +static void _inc_by_one(QString &str, int position) +{ + for (int i = position; i >= 0; i--) + { + char last_char = str[i].latin1(); + switch(last_char) + { + case '0': + str[i] = '1'; + break; + case '1': + str[i] = '2'; + break; + case '2': + str[i] = '3'; + break; + case '3': + str[i] = '4'; + break; + case '4': + str[i] = '5'; + break; + case '5': + str[i] = '6'; + break; + case '6': + str[i] = '7'; + break; + case '7': + str[i] = '8'; + break; + case '8': + str[i] = '9'; + break; + case '9': + str[i] = '0'; + if (i == 0) str.prepend('1'); + continue; + case '.': + continue; + } + break; + } +} + +// Cut off if more digits in fractional part than 'precision' +static void _round(QString &str, int precision) +{ + int decimalSymbolPos = str.find('.'); + + if (decimalSymbolPos == -1) + if (precision == 0) return; + else if (precision > 0) // add dot if missing (and needed) + { + str.append('.'); + decimalSymbolPos = str.length() - 1; + } + + // fill up with more than enough zeroes (in case fractional part too short) + str.append(QString().fill('0', precision)); + + // Now decide whether to round up or down + char last_char = str[decimalSymbolPos + precision + 1].latin1(); + switch (last_char) + { + case '0': + case '1': + case '2': + case '3': + case '4': + // nothing to do, rounding down + break; + case '5': + case '6': + case '7': + case '8': + case '9': + _inc_by_one(str, decimalSymbolPos + precision); + break; + default: + break; + } + + decimalSymbolPos = str.find('.'); + str.truncate(decimalSymbolPos + precision + 1); + + // if precision == 0 delete also '.' + if (precision == 0) str = str.section('.', 0, 0); +} + +QString KLocale::formatNumber(const QString &numStr, bool round, + int precision) const +{ + QString tmpString = numStr; + if ((round && precision < 0) || + ! QRegExp("^[+-]?\\d+(\\.\\d+)*(e[+-]?\\d+)?$").exactMatch(tmpString)) + return numStr; + + + // Skip the sign (for now) + bool neg = (tmpString[0] == '-'); + if (neg || tmpString[0] == '+') tmpString.remove(0, 1); + + // Split off exponential part (including 'e'-symbol) + QString mantString = tmpString.section('e', 0, 0, + QString::SectionCaseInsensitiveSeps); + QString expString = tmpString.section('e', 1, 1, + QString::SectionCaseInsensitiveSeps | + QString::SectionIncludeLeadingSep); + + if (round) _round(mantString, precision); + + // Replace dot with locale decimal separator + mantString.replace(QChar('.'), decimalSymbol()); + + // Insert the thousand separators + _insertSeparator(mantString, thousandsSeparator(), decimalSymbol()); + + // How can we know where we should put the sign? + mantString.prepend(neg?negativeSign():positiveSign()); + + return mantString + expString; +} + +QString KLocale::formatDate(const QDate &pDate, bool shortFormat) const +{ + const QString rst = shortFormat?dateFormatShort():dateFormat(); + + QString buffer; + + if ( ! pDate.isValid() ) return buffer; + + bool escape = false; + + int year = calendar()->year(pDate); + int month = calendar()->month(pDate); + + for ( uint format_index = 0; format_index < rst.length(); ++format_index ) + { + if ( !escape ) + { + if ( rst.at( format_index ).unicode() == '%' ) + escape = true; + else + buffer.append(rst.at(format_index)); + } + else + { + switch ( rst.at( format_index ).unicode() ) + { + case '%': + buffer.append('%'); + break; + case 'Y': + buffer.append(calendar()->yearString(pDate, false)); + break; + case 'y': + buffer.append(calendar()->yearString(pDate, true)); + break; + case 'n': + buffer.append(calendar()->monthString(pDate, true)); + break; + case 'e': + buffer.append(calendar()->dayString(pDate, true)); + break; + case 'm': + buffer.append(calendar()->monthString(pDate, false)); + break; + case 'b': + if (d->nounDeclension && d->dateMonthNamePossessive) + buffer.append(calendar()->monthNamePossessive(month, year, true)); + else + buffer.append(calendar()->monthName(month, year, true)); + break; + case 'B': + if (d->nounDeclension && d->dateMonthNamePossessive) + buffer.append(calendar()->monthNamePossessive(month, year, false)); + else + buffer.append(calendar()->monthName(month, year, false)); + break; + case 'd': + buffer.append(calendar()->dayString(pDate, false)); + break; + case 'a': + buffer.append(calendar()->weekDayName(pDate, true)); + break; + case 'A': + buffer.append(calendar()->weekDayName(pDate, false)); + break; + default: + buffer.append(rst.at(format_index)); + break; + } + escape = false; + } + } + return buffer; +} + +void KLocale::setMainCatalogue(const char *catalog) +{ + maincatalogue = catalog; +} + +double KLocale::readNumber(const QString &_str, bool * ok) const +{ + QString str = _str.stripWhiteSpace(); + bool neg = str.find(negativeSign()) == 0; + if (neg) + str.remove( 0, negativeSign().length() ); + + /* will hold the scientific notation portion of the number. + Example, with 2.34E+23, exponentialPart == "E+23" + */ + QString exponentialPart; + int EPos; + + EPos = str.find('E', 0, false); + + if (EPos != -1) + { + exponentialPart = str.mid(EPos); + str = str.left(EPos); + } + + int pos = str.find(decimalSymbol()); + QString major; + QString minor; + if ( pos == -1 ) + major = str; + else + { + major = str.left(pos); + minor = str.mid(pos + decimalSymbol().length()); + } + + // Remove thousand separators + int thlen = thousandsSeparator().length(); + int lastpos = 0; + while ( ( pos = major.find( thousandsSeparator() ) ) > 0 ) + { + // e.g. 12,,345,,678,,922 Acceptable positions (from the end) are 5, 10, 15... i.e. (3+thlen)*N + int fromEnd = major.length() - pos; + if ( fromEnd % (3+thlen) != 0 // Needs to be a multiple, otherwise it's an error + || pos - lastpos > 3 // More than 3 digits between two separators -> error + || pos == 0 // Can't start with a separator + || (lastpos>0 && pos-lastpos!=3)) // Must have exactly 3 digits between two separators + { + if (ok) *ok = false; + return 0.0; + } + + lastpos = pos; + major.remove( pos, thlen ); + } + if (lastpos>0 && major.length()-lastpos!=3) // Must have exactly 3 digits after the last separator + { + if (ok) *ok = false; + return 0.0; + } + + QString tot; + if (neg) tot = '-'; + + tot += major + '.' + minor + exponentialPart; + + return tot.toDouble(ok); +} + +double KLocale::readMoney(const QString &_str, bool * ok) const +{ + QString str = _str.stripWhiteSpace(); + bool neg = false; + bool currencyFound = false; + QString symbol = currencySymbol(); + // First try removing currency symbol from either end + int pos = str.find(symbol); + if ( pos == 0 || pos == (int) str.length()-symbol.length() ) + { + str.remove(pos,symbol.length()); + str = str.stripWhiteSpace(); + currencyFound = true; + } + if (str.isEmpty()) + { + if (ok) *ok = false; + return 0; + } + // Then try removing negative sign from either end + // (with a special case for parenthesis) + if (negativeMonetarySignPosition() == ParensAround) + { + if (str[0] == '(' && str[str.length()-1] == ')') + { + neg = true; + str.remove(str.length()-1,1); + str.remove(0,1); + } + } + else + { + int i1 = str.find(negativeSign()); + if ( i1 == 0 || i1 == (int) str.length()-1 ) + { + neg = true; + str.remove(i1,negativeSign().length()); + } + } + if (neg) str = str.stripWhiteSpace(); + + // Finally try again for the currency symbol, if we didn't find + // it already (because of the negative sign being in the way). + if ( !currencyFound ) + { + pos = str.find(symbol); + if ( pos == 0 || pos == (int) str.length()-symbol.length() ) + { + str.remove(pos,symbol.length()); + str = str.stripWhiteSpace(); + } + } + + // And parse the rest as a number + pos = str.find(monetaryDecimalSymbol()); + QString major; + QString minior; + if (pos == -1) + major = str; + else + { + major = str.left(pos); + minior = str.mid(pos + monetaryDecimalSymbol().length()); + } + + // Remove thousand separators + int thlen = monetaryThousandsSeparator().length(); + int lastpos = 0; + while ( ( pos = major.find( monetaryThousandsSeparator() ) ) > 0 ) + { + // e.g. 12,,345,,678,,922 Acceptable positions (from the end) are 5, 10, 15... i.e. (3+thlen)*N + int fromEnd = major.length() - pos; + if ( fromEnd % (3+thlen) != 0 // Needs to be a multiple, otherwise it's an error + || pos - lastpos > 3 // More than 3 digits between two separators -> error + || pos == 0 // Can't start with a separator + || (lastpos>0 && pos-lastpos!=3)) // Must have exactly 3 digits between two separators + { + if (ok) *ok = false; + return 0.0; + } + lastpos = pos; + major.remove( pos, thlen ); + } + if (lastpos>0 && major.length()-lastpos!=3) // Must have exactly 3 digits after the last separator + { + if (ok) *ok = false; + return 0.0; + } + + QString tot; + if (neg) tot = '-'; + tot += major + '.' + minior; + return tot.toDouble(ok); +} + +/** + * helper function to read integers + * @param str + * @param pos the position to start at. It will be updated when we parse it. + * @return the integer read in the string, or -1 if no string + */ +static int readInt(const QString &str, uint &pos) +{ + if (!str.at(pos).isDigit()) return -1; + int result = 0; + for (; str.length() > pos && str.at(pos).isDigit(); pos++) + { + result *= 10; + result += str.at(pos).digitValue(); + } + + return result; +} + +QDate KLocale::readDate(const QString &intstr, bool* ok) const +{ + QDate date; + date = readDate(intstr, ShortFormat, ok); + if (date.isValid()) return date; + return readDate(intstr, NormalFormat, ok); +} + +QDate KLocale::readDate(const QString &intstr, ReadDateFlags flags, bool* ok) const +{ + QString fmt = ((flags & ShortFormat) ? dateFormatShort() : dateFormat()).simplifyWhiteSpace(); + return readDate( intstr, fmt, ok ); +} + +QDate KLocale::readDate(const QString &intstr, const QString &fmt, bool* ok) const +{ + //kdDebug() << "KLocale::readDate intstr=" << intstr << " fmt=" << fmt << endl; + QString str = intstr.simplifyWhiteSpace().lower(); + int day = -1, month = -1; + // allow the year to be omitted if not in the format + int year = calendar()->year(QDate::currentDate()); + uint strpos = 0; + uint fmtpos = 0; + + int iLength; // Temporary variable used when reading input + + bool error = false; + + while (fmt.length() > fmtpos && str.length() > strpos && !error) + { + + QChar c = fmt.at(fmtpos++); + + if (c != '%') { + if (c.isSpace() && str.at(strpos).isSpace()) + strpos++; + else if (c != str.at(strpos++)) + error = true; + } + else + { + int j; + // remove space at the beginning + if (str.length() > strpos && str.at(strpos).isSpace()) + strpos++; + + c = fmt.at(fmtpos++); + switch (c) + { + case 'a': + case 'A': + + error = true; + j = 1; + while (error && (j < 8)) { + QString s = calendar()->weekDayName(j, c == 'a').lower(); + int len = s.length(); + if (str.mid(strpos, len) == s) + { + strpos += len; + error = false; + } + j++; + } + break; + case 'b': + case 'B': + + error = true; + if (d->nounDeclension && d->dateMonthNamePossessive) { + j = 1; + while (error && (j < 13)) { + QString s = calendar()->monthNamePossessive(j, year, c == 'b').lower(); + int len = s.length(); + if (str.mid(strpos, len) == s) { + month = j; + strpos += len; + error = false; + } + j++; + } + } + j = 1; + while (error && (j < 13)) { + QString s = calendar()->monthName(j, year, c == 'b').lower(); + int len = s.length(); + if (str.mid(strpos, len) == s) { + month = j; + strpos += len; + error = false; + } + j++; + } + break; + case 'd': + case 'e': + day = calendar()->dayStringToInteger(str.mid(strpos), iLength); + strpos += iLength; + + error = iLength <= 0; + break; + + case 'n': + case 'm': + month = calendar()->monthStringToInteger(str.mid(strpos), iLength); + strpos += iLength; + + error = iLength <= 0; + break; + + case 'Y': + case 'y': + year = calendar()->yearStringToInteger(str.mid(strpos), iLength); + strpos += iLength; + + error = iLength <= 0; + break; + } + } + } + + /* for a match, we should reach the end of both strings, not just one of + them */ + if ( fmt.length() > fmtpos || str.length() > strpos ) + { + error = true; + } + + //kdDebug(173) << "KLocale::readDate day=" << day << " month=" << month << " year=" << year << endl; + if ( year != -1 && month != -1 && day != -1 && !error) + { + if (ok) *ok = true; + + QDate result; + calendar()->setYMD(result, year, month, day); + + return result; + } + else + { + if (ok) *ok = false; + return QDate(); // invalid date + } +} + +QTime KLocale::readTime(const QString &intstr, bool *ok) const +{ + QTime _time; + _time = readTime(intstr, WithSeconds, ok); + if (_time.isValid()) return _time; + return readTime(intstr, WithoutSeconds, ok); +} + +QTime KLocale::readTime(const QString &intstr, ReadTimeFlags flags, bool *ok) const +{ + QString str = intstr.simplifyWhiteSpace().lower(); + QString Format = timeFormat().simplifyWhiteSpace(); + if (flags & WithoutSeconds) + Format.remove(QRegExp(".%S")); + + int hour = -1, minute = -1; + int second = ( (flags & WithoutSeconds) == 0 ) ? -1 : 0; // don't require seconds + bool g_12h = false; + bool pm = false; + uint strpos = 0; + uint Formatpos = 0; + + while (Format.length() > Formatpos || str.length() > strpos) + { + if ( !(Format.length() > Formatpos && str.length() > strpos) ) goto error; + + QChar c = Format.at(Formatpos++); + + if (c != '%') + { + if (c.isSpace()) + strpos++; + else if (c != str.at(strpos++)) + goto error; + continue; + } + + // remove space at the beginning + if (str.length() > strpos && str.at(strpos).isSpace()) + strpos++; + + c = Format.at(Formatpos++); + switch (c) + { + case 'p': + { + QString s; + s = translate("pm").lower(); + int len = s.length(); + if (str.mid(strpos, len) == s) + { + pm = true; + strpos += len; + } + else + { + s = translate("am").lower(); + len = s.length(); + if (str.mid(strpos, len) == s) { + pm = false; + strpos += len; + } + else + goto error; + } + } + break; + + case 'k': + case 'H': + g_12h = false; + hour = readInt(str, strpos); + if (hour < 0 || hour > 23) + goto error; + + break; + + case 'l': + case 'I': + g_12h = true; + hour = readInt(str, strpos); + if (hour < 1 || hour > 12) + goto error; + + break; + + case 'M': + minute = readInt(str, strpos); + if (minute < 0 || minute > 59) + goto error; + + break; + + case 'S': + second = readInt(str, strpos); + if (second < 0 || second > 59) + goto error; + + break; + } + } + if (g_12h) { + hour %= 12; + if (pm) hour += 12; + } + + if (ok) *ok = true; + return QTime(hour, minute, second); + + error: + if (ok) *ok = false; + // ######## KDE4: remove this + return QTime(-1, -1, -1); // return invalid date if it didn't work +} + +//BIC: merge with below +QString KLocale::formatTime(const QTime &pTime, bool includeSecs) const +{ + return formatTime( pTime, includeSecs, false ); +} + +QString KLocale::formatTime(const QTime &pTime, bool includeSecs, bool isDuration) const +{ + const QString rst = timeFormat(); + + // only "pm/am" here can grow, the rest shrinks, but + // I'm rather safe than sorry + QChar *buffer = new QChar[rst.length() * 3 / 2 + 30]; + + uint index = 0; + bool escape = false; + int number = 0; + + for ( uint format_index = 0; format_index < rst.length(); format_index++ ) + { + if ( !escape ) + { + if ( rst.at( format_index ).unicode() == '%' ) + escape = true; + else + buffer[index++] = rst.at( format_index ); + } + else + { + switch ( rst.at( format_index ).unicode() ) + { + case '%': + buffer[index++] = '%'; + break; + case 'H': + put_it_in( buffer, index, pTime.hour() ); + break; + case 'I': + if ( isDuration ) + put_it_in( buffer, index, pTime.hour() ); + else + put_it_in( buffer, index, ( pTime.hour() + 11) % 12 + 1 ); + break; + case 'M': + put_it_in( buffer, index, pTime.minute() ); + break; + case 'S': + if (includeSecs) + put_it_in( buffer, index, pTime.second() ); + else if ( index > 0 ) + { + // we remove the separator sign before the seconds and + // assume that works everywhere + --index; + break; + } + break; + case 'k': + number = pTime.hour(); + case 'l': + // to share the code + if ( rst.at( format_index ).unicode() == 'l' ) + number = isDuration ? pTime.hour() : (pTime.hour() + 11) % 12 + 1; + if ( number / 10 ) + buffer[index++] = number / 10 + '0'; + buffer[index++] = number % 10 + '0'; + break; + case 'p': + if ( !isDuration ) + { + QString s; + if ( pTime.hour() >= 12 ) + put_it_in( buffer, index, translate("pm") ); + else + put_it_in( buffer, index, translate("am") ); + } + break; + default: + buffer[index++] = rst.at( format_index ); + break; + } + escape = false; + } + } + QString ret( buffer, index ); + delete [] buffer; + if ( isDuration ) // eliminate trailing-space due to " %p" + return ret.stripWhiteSpace(); + else + return ret; +} + +bool KLocale::use12Clock() const +{ + if ((timeFormat().contains(QString::fromLatin1("%I")) > 0) || + (timeFormat().contains(QString::fromLatin1("%l")) > 0)) + return true; + else + return false; +} + +QString KLocale::languages() const +{ + return d->languageList.join( QString::fromLatin1(":") ); +} + +QStringList KLocale::languageList() const +{ + return d->languageList; +} + +QString KLocale::formatDateTime(const QDateTime &pDateTime, + bool shortFormat, + bool includeSeconds) const +{ + return translate("concatenation of dates and time", "%1 %2") + .arg( formatDate( pDateTime.date(), shortFormat ) ) + .arg( formatTime( pDateTime.time(), includeSeconds ) ); +} + +QString i18n(const char* text) +{ + register KLocale *instance = KGlobal::locale(); + if (instance) + return instance->translate(text); + return QString::fromUtf8(text); +} + +QString i18n(const char* index, const char *text) +{ + register KLocale *instance = KGlobal::locale(); + if (instance) + return instance->translate(index, text); + return QString::fromUtf8(text); +} + +QString i18n(const char* singular, const char* plural, unsigned long n) +{ + register KLocale *instance = KGlobal::locale(); + if (instance) + return instance->translate(singular, plural, n); + if (n == 1) + return put_n_in(QString::fromUtf8(singular), n); + else + return put_n_in(QString::fromUtf8(plural), n); +} + +void KLocale::initInstance() +{ + if (KGlobal::_locale) + return; + + KInstance *app = KGlobal::instance(); + if (app) { + KGlobal::_locale = new KLocale(QString::fromLatin1(app->instanceName())); + + // only do this for the global instance + QTextCodec::setCodecForLocale(KGlobal::_locale->codecForEncoding()); + } + else + kdDebug(173) << "no app name available using KLocale - nothing to do\n"; +} + +QString KLocale::langLookup(const QString &fname, const char *rtype) +{ + QStringList search; + + // assemble the local search paths + const QStringList localDoc = KGlobal::dirs()->resourceDirs(rtype); + + // look up the different languages + for (int id=localDoc.count()-1; id >= 0; --id) + { + QStringList langs = KGlobal::locale()->languageList(); + langs.append( "en" ); + langs.remove( defaultLanguage() ); + QStringList::ConstIterator lang; + for (lang = langs.begin(); lang != langs.end(); ++lang) + search.append(QString("%1%2/%3").arg(localDoc[id]).arg(*lang).arg(fname)); + } + + // try to locate the file + QStringList::Iterator it; + for (it = search.begin(); it != search.end(); ++it) + { + kdDebug(173) << "Looking for help in: " << *it << endl; + + QFileInfo info(*it); + if (info.exists() && info.isFile() && info.isReadable()) + return *it; + } + + return QString::null; +} + +bool KLocale::useDefaultLanguage() const +{ + return language() == defaultLanguage(); +} + +void KLocale::initEncoding(KConfig *) +{ + const int mibDefault = 4; // ISO 8859-1 + + // This all made more sense when we still had the EncodingEnum config key. + setEncoding( QTextCodec::codecForLocale()->mibEnum() ); + + if ( !d->codecForEncoding ) + { + kdWarning(173) << " Defaulting to ISO 8859-1 encoding." << endl; + setEncoding(mibDefault); + } + + Q_ASSERT( d->codecForEncoding ); +} + +void KLocale::initFileNameEncoding(KConfig *) +{ + // If the following environment variable is set, assume all filenames + // are in UTF-8 regardless of the current C locale. + d->utf8FileEncoding = getenv("KDE_UTF8_FILENAMES") != 0; + if (d->utf8FileEncoding) + { + QFile::setEncodingFunction(KLocale::encodeFileNameUTF8); + QFile::setDecodingFunction(KLocale::decodeFileNameUTF8); + } + // Otherwise, stay with QFile's default filename encoding functions + // which, on Unix platforms, use the locale's codec. +} + +QCString KLocale::encodeFileNameUTF8( const QString & fileName ) +{ + return fileName.utf8(); +} + +QString KLocale::decodeFileNameUTF8( const QCString & localFileName ) +{ + return QString::fromUtf8(localFileName); +} + +void KLocale::setDateFormat(const QString & format) +{ + doFormatInit(); + m_dateFormat = format.stripWhiteSpace(); +} + +void KLocale::setDateFormatShort(const QString & format) +{ + doFormatInit(); + m_dateFormatShort = format.stripWhiteSpace(); +} + +void KLocale::setDateMonthNamePossessive(bool possessive) +{ + doFormatInit(); + d->dateMonthNamePossessive = possessive; +} + +void KLocale::setTimeFormat(const QString & format) +{ + doFormatInit(); + m_timeFormat = format.stripWhiteSpace(); +} + +void KLocale::setWeekStartsMonday(bool start) //deprecated +{ + doFormatInit(); + if (start) + d->weekStartDay = 1; + else + d->weekStartDay = 7; +} + +void KLocale::setWeekStartDay(int day) +{ + doFormatInit(); + if (day>7 || day<1) + d->weekStartDay = 1; //Monday is default + else + d->weekStartDay = day; +} + +QString KLocale::dateFormat() const +{ + doFormatInit(); + return m_dateFormat; +} + +QString KLocale::dateFormatShort() const +{ + doFormatInit(); + return m_dateFormatShort; +} + +QString KLocale::timeFormat() const +{ + doFormatInit(); + return m_timeFormat; +} + +void KLocale::setDecimalSymbol(const QString & symbol) +{ + doFormatInit(); + m_decimalSymbol = symbol.stripWhiteSpace(); +} + +void KLocale::setThousandsSeparator(const QString & separator) +{ + doFormatInit(); + // allow spaces here + m_thousandsSeparator = separator; +} + +void KLocale::setPositiveSign(const QString & sign) +{ + doFormatInit(); + m_positiveSign = sign.stripWhiteSpace(); +} + +void KLocale::setNegativeSign(const QString & sign) +{ + doFormatInit(); + m_negativeSign = sign.stripWhiteSpace(); +} + +void KLocale::setPositiveMonetarySignPosition(SignPosition signpos) +{ + doFormatInit(); + m_positiveMonetarySignPosition = signpos; +} + +void KLocale::setNegativeMonetarySignPosition(SignPosition signpos) +{ + doFormatInit(); + m_negativeMonetarySignPosition = signpos; +} + +void KLocale::setPositivePrefixCurrencySymbol(bool prefix) +{ + doFormatInit(); + m_positivePrefixCurrencySymbol = prefix; +} + +void KLocale::setNegativePrefixCurrencySymbol(bool prefix) +{ + doFormatInit(); + m_negativePrefixCurrencySymbol = prefix; +} + +void KLocale::setFracDigits(int digits) +{ + doFormatInit(); + m_fracDigits = digits; +} + +void KLocale::setMonetaryThousandsSeparator(const QString & separator) +{ + doFormatInit(); + // allow spaces here + m_monetaryThousandsSeparator = separator; +} + +void KLocale::setMonetaryDecimalSymbol(const QString & symbol) +{ + doFormatInit(); + m_monetaryDecimalSymbol = symbol.stripWhiteSpace(); +} + +void KLocale::setCurrencySymbol(const QString & symbol) +{ + doFormatInit(); + m_currencySymbol = symbol.stripWhiteSpace(); +} + +int KLocale::pageSize() const +{ + doFormatInit(); + return d->pageSize; +} + +void KLocale::setPageSize(int pageSize) +{ + // #### check if it's in range?? + doFormatInit(); + d->pageSize = pageSize; +} + +KLocale::MeasureSystem KLocale::measureSystem() const +{ + doFormatInit(); + return d->measureSystem; +} + +void KLocale::setMeasureSystem(MeasureSystem value) +{ + doFormatInit(); + d->measureSystem = value; +} + +QString KLocale::defaultLanguage() +{ + return QString::fromLatin1("en_US"); +} + +QString KLocale::defaultCountry() +{ + return QString::fromLatin1("C"); +} + +const char * KLocale::encoding() const +{ +#ifdef Q_WS_WIN + if (0==qstrcmp("System", codecForEncoding()->name())) + { + //win32 returns "System" codec name here but KDE apps expect a real name: + strcpy(d->win32SystemEncoding, "cp "); + if (GetLocaleInfoA( MAKELCID(MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), SORT_DEFAULT), + LOCALE_IDEFAULTANSICODEPAGE, d->win32SystemEncoding+3, sizeof(d->win32SystemEncoding)-3-1 )) + { + return d->win32SystemEncoding; + } + } +#endif + return codecForEncoding()->name(); +} + +int KLocale::encodingMib() const +{ + return codecForEncoding()->mibEnum(); +} + +int KLocale::fileEncodingMib() const +{ + if (d->utf8FileEncoding) + return 106; + return codecForEncoding()->mibEnum(); +} + +QTextCodec * KLocale::codecForEncoding() const +{ + return d->codecForEncoding; +} + +bool KLocale::setEncoding(int mibEnum) +{ + QTextCodec * codec = QTextCodec::codecForMib(mibEnum); + if (codec) + d->codecForEncoding = codec; + + return codec != 0; +} + +QStringList KLocale::languagesTwoAlpha() const +{ + if (d->langTwoAlpha.count()) + return d->langTwoAlpha; + + const QStringList &origList = languageList(); + + QStringList result; + + KConfig config(QString::fromLatin1("language.codes"), true, false); + config.setGroup("TwoLetterCodes"); + + for ( QStringList::ConstIterator it = origList.begin(); + it != origList.end(); + ++it ) + { + QString lang = *it; + QStringList langLst; + if (config.hasKey( lang )) + langLst = config.readListEntry( lang ); + else + { + int i = lang.find('_'); + if (i >= 0) + lang.truncate(i); + langLst << lang; + } + + for ( QStringList::ConstIterator langIt = langLst.begin(); + langIt != langLst.end(); + ++langIt ) + { + if ( !(*langIt).isEmpty() && !result.contains( *langIt ) ) + result += *langIt; + } + } + d->langTwoAlpha = result; + return result; +} + +QStringList KLocale::allLanguagesTwoAlpha() const +{ + if (!d->languages) + d->languages = new KConfig("all_languages", true, false, "locale"); + + return d->languages->groupList(); +} + +QString KLocale::twoAlphaToLanguageName(const QString &code) const +{ + if (!d->languages) + d->languages = new KConfig("all_languages", true, false, "locale"); + + QString groupName = code; + const int i = groupName.find('_'); + groupName.replace(0, i, groupName.left(i).lower()); + + d->languages->setGroup(groupName); + return d->languages->readEntry("Name"); +} + +QStringList KLocale::allCountriesTwoAlpha() const +{ + QStringList countries; + QStringList paths = KGlobal::dirs()->findAllResources("locale", "l10n/*/entry.desktop"); + for(QStringList::ConstIterator it = paths.begin(); + it != paths.end(); ++it) + { + QString code = (*it).mid((*it).length()-16, 2); + if (code != "/C") + countries.append(code); + } + return countries; +} + +QString KLocale::twoAlphaToCountryName(const QString &code) const +{ + KConfig cfg("l10n/"+code.lower()+"/entry.desktop", true, false, "locale"); + cfg.setGroup("KCM Locale"); + return cfg.readEntry("Name"); +} + +void KLocale::setCalendar(const QString & calType) +{ + doFormatInit(); + + d->calendarType = calType; + + delete d->calendar; + d->calendar = 0; +} + +QString KLocale::calendarType() const +{ + doFormatInit(); + + return d->calendarType; +} + +const KCalendarSystem * KLocale::calendar() const +{ + doFormatInit(); + + // Check if it's the correct calendar?!? + if ( !d->calendar ) + d->calendar = KCalendarSystemFactory::create( d->calendarType, this ); + + return d->calendar; +} + +KLocale::KLocale(const KLocale & rhs) +{ + d = new KLocalePrivate; + + *this = rhs; +} + +KLocale & KLocale::operator=(const KLocale & rhs) +{ + // Numbers and money + m_decimalSymbol = rhs.m_decimalSymbol; + m_thousandsSeparator = rhs.m_thousandsSeparator; + m_currencySymbol = rhs.m_currencySymbol; + m_monetaryDecimalSymbol = rhs.m_monetaryDecimalSymbol; + m_monetaryThousandsSeparator = rhs.m_monetaryThousandsSeparator; + m_positiveSign = rhs.m_positiveSign; + m_negativeSign = rhs.m_negativeSign; + m_fracDigits = rhs.m_fracDigits; + m_positivePrefixCurrencySymbol = rhs.m_positivePrefixCurrencySymbol; + m_negativePrefixCurrencySymbol = rhs.m_negativePrefixCurrencySymbol; + m_positiveMonetarySignPosition = rhs.m_positiveMonetarySignPosition; + m_negativeMonetarySignPosition = rhs.m_negativeMonetarySignPosition; + + // Date and time + m_timeFormat = rhs.m_timeFormat; + m_dateFormat = rhs.m_dateFormat; + m_dateFormatShort = rhs.m_dateFormatShort; + + m_language = rhs.m_language; + m_country = rhs.m_country; + + // the assignment operator works here + *d = *rhs.d; + d->languages = 0; // Don't copy languages + d->calendar = 0; // Don't copy the calendar + + return *this; +} + +bool KLocale::setCharset(const QString & ) { return true; } +QString KLocale::charset() const { return QString::fromLatin1("UTF-8"); } + +// KDE4: remove +#if 0 +void nothing() { i18n("&Next"); } +#endif -- cgit v1.2.3