kdecore Library API Documentation

klocale.cpp

00001 // -*- c-basic-offset: 2 -*-
00002 /* This file is part of the KDE libraries
00003    Copyright (c) 1997,2001 Stephan Kulow <coolo@kde.org>
00004    Copyright (c) 1999 Preston Brown <pbrown@kde.org>
00005    Copyright (c) 1999-2002 Hans Petter Bieker <bieker@kde.org>
00006    Copyright (c) 2002 Lukas Tinkl <lukas@kde.org>
00007 
00008    This library is free software; you can redistribute it and/or
00009    modify it under the terms of the GNU Library General Public
00010    License as published by the Free Software Foundation; either
00011    version 2 of the License, or (at your option) any later version.
00012 
00013    This library is distributed in the hope that it will be useful,
00014    but WITHOUT ANY WARRANTY; without even the implied warranty of
00015    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016    Library General Public License for more details.
00017 
00018    You should have received a copy of the GNU Library General Public License
00019    along with this library; see the file COPYING.LIB.  If not, write to
00020    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00021    Boston, MA 02111-1307, USA.
00022 */
00023 
00024 #include <config.h>
00025 
00026 #include <stdlib.h> // getenv
00027 
00028 #include <qtextcodec.h>
00029 #include <qfile.h>
00030 #include <qprinter.h>
00031 #include <qdatetime.h>
00032 #include <qfileinfo.h>
00033 #include <qregexp.h>
00034 
00035 #include "kcatalogue.h"
00036 #include "kglobal.h"
00037 #include "kstandarddirs.h"
00038 #include "ksimpleconfig.h"
00039 #include "kinstance.h"
00040 #include "kconfig.h"
00041 #include "kdebug.h"
00042 #include "kcalendarsystem.h"
00043 #include "kcalendarsystemfactory.h"
00044 #include "klocale.h"
00045 
00046 static const char * const SYSTEM_MESSAGES = "kdelibs";
00047 
00048 static const char *maincatalogue = 0;
00049 
00050 class KLocalePrivate
00051 {
00052 public:
00053   int weekStartDay;
00054   int plural_form;
00055   bool nounDeclension;
00056   bool dateMonthNamePossessive;
00057   QStringList languageList;
00058   QValueList<KCatalogue> catalogues;
00059   QString encoding;
00060   QTextCodec * codecForEncoding;
00061   KConfig * config;
00062   bool formatInited;
00063   int /*QPrinter::PageSize*/ pageSize;
00064   KLocale::MeasureSystem measureSystem;
00065   QStringList langTwoAlpha;
00066   KConfig *languages;
00067 
00068   QString calendarType;
00069   KCalendarSystem * calendar;
00070   QString first_language;
00071   bool utf8FileEncoding;
00072 };
00073 
00074 static KLocale *this_klocale = 0;
00075 
00076 KLocale::KLocale( const QString & catalog, KConfig * config )
00077 {
00078   d = new KLocalePrivate;
00079   d->config = config;
00080   d->languages = 0;
00081   d->calendar = 0;
00082 
00083   initCatalogue(catalog);
00084   initEncoding(0);
00085   initFileNameEncoding(0);
00086 
00087   KConfig *cfg = d->config;
00088   this_klocale = this;
00089   if (!cfg) cfg = KGlobal::instance()->config();
00090   this_klocale = 0;
00091   Q_ASSERT( cfg );
00092 
00093   if (m_language.isEmpty())
00094      initLanguage(cfg, config == 0);
00095 }
00096 
00097 QString KLocale::_initLanguage(KConfigBase *config)
00098 {
00099   if (this_klocale)
00100   {
00101      // ### HPB Why this cast??
00102      this_klocale->initLanguage((KConfig *) config, true);
00103      return this_klocale->language();
00104   }
00105   return QString::null;
00106 }
00107 
00108 void KLocale::initCatalogue(const QString & catalog)
00109 {
00110   // Use the first non-null string.
00111   QString mainCatalogue = catalog;
00112   if (maincatalogue)
00113     mainCatalogue = QString::fromLatin1(maincatalogue);
00114 
00115   if (mainCatalogue.isEmpty()) {
00116     kdDebug(173) << "KLocale instance created called without valid "
00117                  << "catalog! Give an argument or call setMainCatalogue "
00118                  << "before init" << endl;
00119   }
00120   else
00121     d->catalogues.append( KCatalogue(mainCatalogue ) );
00122 
00123   // always include kdelibs's mo files
00124   d->catalogues.append( KCatalogue( SYSTEM_MESSAGES ) );
00125   d->catalogues.append( KCatalogue( "kio" ) );
00126 }
00127 
00128 void KLocale::initLanguage(KConfig * config, bool useEnv)
00129 {
00130   KConfigGroupSaver saver(config, "Locale");
00131 
00132   m_country = config->readEntry( "Country" );
00133   if ( m_country.isEmpty() )
00134     m_country = defaultCountry();
00135 
00136   // Reset the list and add the new languages
00137   QStringList languageList;
00138   if ( useEnv )
00139     languageList += QStringList::split
00140       (':', QFile::decodeName( ::getenv("KDE_LANG") ));
00141 
00142   languageList += config->readListEntry("Language", ':');
00143 
00144   // same order as setlocale use
00145   if ( useEnv )
00146     {
00147       // HPB: Only run splitLocale on the environment variables..
00148       QStringList langs;
00149 
00150       langs << QFile::decodeName( ::getenv("LC_ALL") );
00151       langs << QFile::decodeName( ::getenv("LC_MESSAGES") );
00152       langs << QFile::decodeName( ::getenv("LANG") );
00153       langs << QFile::decodeName( ::getenv("LC_CTYPE") );
00154 
00155       for ( QStringList::Iterator it = langs.begin();
00156         it != langs.end();
00157         ++it )
00158     {
00159       QString ln, ct, chrset;
00160       splitLocale(*it, ln, ct, chrset);
00161 
00162       if (!ct.isEmpty()) {
00163         langs.insert(it, ln + '_' + ct);
00164         if (!chrset.isEmpty())
00165           langs.insert(it, ln + '_' + ct + '.' + chrset);
00166       }
00167 
00168           langs.insert(it, ln);
00169     }
00170 
00171       languageList += langs;
00172     }
00173 
00174   // now we have a language list -- let's use the first OK language
00175   setLanguage( languageList );
00176 }
00177 
00178 void KLocale::doBindInit()
00179 {
00180   for ( QValueList<KCatalogue>::Iterator it = d->catalogues.begin();
00181     it != d->catalogues.end();
00182     ++it )
00183     initCatalogue( *it );
00184 
00185   if ( useDefaultLanguage() )
00186     d->plural_form = -1;
00187   else
00188     {
00189       QString pf = translate_priv
00190     ( I18N_NOOP("_: Dear translator, please do not translate this string "
00191             "in any form, but pick the _right_ value out of "
00192             "NoPlural/TwoForms/French... If not sure what to do mail "
00193             "thd@kde.org and coolo@kde.org, they will tell you. "
00194             "Better leave that out if unsure, the programs will "
00195             "crash!!\nDefinition of PluralForm - to be set by the "
00196             "translator of kdelibs.po"), 0);
00197       if ( pf.isEmpty() ) {
00198     kdWarning(173) << "found no definition of PluralForm for " << m_language << endl;
00199     d->plural_form = -1;
00200       } else if ( pf == "NoPlural" )
00201     d->plural_form = 0;
00202       else if ( pf == "TwoForms" )
00203     d->plural_form = 1;
00204       else if ( pf == "French" )
00205     d->plural_form = 2;
00206       else if ( pf == "OneTwoRest" || pf == "Gaeilge" ) // Gaelige is the old name
00207     d->plural_form = 3;
00208       else if ( pf == "Russian" )
00209     d->plural_form = 4;
00210       else if ( pf == "Polish" )
00211     d->plural_form = 5;
00212       else if ( pf == "Slovenian" )
00213     d->plural_form = 6;
00214       else if ( pf == "Lithuanian" )
00215     d->plural_form = 7;
00216       else if ( pf == "Czech" )
00217     d->plural_form = 8;
00218       else if ( pf == "Slovak" )
00219     d->plural_form = 9;
00220       else if ( pf == "Maltese" )
00221     d->plural_form = 10;
00222       else if ( pf == "Arabic" )
00223     d->plural_form = 11;
00224       else if ( pf == "Balcan" )
00225     d->plural_form = 12;
00226       else if ( pf == "Macedonian" )
00227     d->plural_form = 13;
00228       else {
00229     kdWarning(173) << "Definition of PluralForm is none of "
00230                << "NoPlural/"
00231                << "TwoForms/"
00232                << "French/"
00233                << "OneTwoRest/"
00234                << "Russian/"
00235                << "Polish/"
00236                << "Slovenian/"
00237                << "Lithuanian/"
00238                << "Czech/"
00239                << "Slovak/"
00240                << "Arabic/"
00241                << "Balcan/"
00242                << "Macedonian/"
00243                << "Maltese: " << pf << endl;
00244     exit(1);
00245       }
00246     }
00247 
00248   d->formatInited = false;
00249 }
00250 
00251 void KLocale::doFormatInit() const
00252 {
00253   if ( d->formatInited ) return;
00254 
00255   KLocale * that = const_cast<KLocale *>(this);
00256   that->initFormat();
00257 
00258   d->formatInited = true;
00259 }
00260 
00261 void KLocale::initFormat()
00262 {
00263   KConfig *config = d->config;
00264   if (!config) config = KGlobal::instance()->config();
00265   Q_ASSERT( config );
00266 
00267   kdDebug(173) << "KLocale::initFormat" << endl;
00268 
00269   // make sure the config files are read using the correct locale
00270   // ### Why not add a KConfigBase::setLocale( const KLocale * )?
00271   // ### Then we could remove this hack
00272   KLocale *lsave = KGlobal::_locale;
00273   KGlobal::_locale = this;
00274 
00275   KConfigGroupSaver saver(config, "Locale");
00276 
00277   KSimpleConfig entry(locate("locale",
00278                              QString::fromLatin1("l10n/%1/entry.desktop")
00279                              .arg(m_country)), true);
00280   entry.setGroup("KCM Locale");
00281 
00282   // Numeric
00283 #define readConfigEntry(key, default, save) \
00284   save = entry.readEntry(key, QString::fromLatin1(default)); \
00285   save = config->readEntry(key, save);
00286 
00287 #define readConfigNumEntry(key, default, save, type) \
00288   save = (type)entry.readNumEntry(key, default); \
00289   save = (type)config->readNumEntry(key, save);
00290 
00291 #define readConfigBoolEntry(key, default, save) \
00292   save = entry.readBoolEntry(key, default); \
00293   save = config->readBoolEntry(key, save);
00294 
00295   readConfigEntry("DecimalSymbol", ".", m_decimalSymbol);
00296   readConfigEntry("ThousandsSeparator", ",", m_thousandsSeparator);
00297   m_thousandsSeparator.replace( QString::fromLatin1("$0"), QString::null );
00298   //kdDebug(173) << "m_thousandsSeparator=" << m_thousandsSeparator << endl;
00299 
00300   readConfigEntry("PositiveSign", "", m_positiveSign);
00301   readConfigEntry("NegativeSign", "-", m_negativeSign);
00302 
00303   // Monetary
00304   readConfigEntry("CurrencySymbol", "$", m_currencySymbol);
00305   readConfigEntry("MonetaryDecimalSymbol", ".", m_monetaryDecimalSymbol);
00306   readConfigEntry("MonetaryThousandsSeparator", ",",
00307           m_monetaryThousandsSeparator);
00308   m_monetaryThousandsSeparator.replace(QString::fromLatin1("$0"), QString::null);
00309 
00310   readConfigNumEntry("FracDigits", 2, m_fracDigits, int);
00311   readConfigBoolEntry("PositivePrefixCurrencySymbol", true,
00312               m_positivePrefixCurrencySymbol);
00313   readConfigBoolEntry("NegativePrefixCurrencySymbol", true,
00314               m_negativePrefixCurrencySymbol);
00315   readConfigNumEntry("PositiveMonetarySignPosition", (int)BeforeQuantityMoney,
00316              m_positiveMonetarySignPosition, SignPosition);
00317   readConfigNumEntry("NegativeMonetarySignPosition", (int)ParensAround,
00318              m_negativeMonetarySignPosition, SignPosition);
00319 
00320 
00321   // Date and time
00322   readConfigEntry("TimeFormat", "%H:%M:%S", m_timeFormat);
00323   readConfigEntry("DateFormat", "%A %d %B %Y", m_dateFormat);
00324   readConfigEntry("DateFormatShort", "%Y-%m-%d", m_dateFormatShort);
00325   readConfigNumEntry("WeekStartDay", 1, d->weekStartDay, int);
00326 
00327   // other
00328   readConfigNumEntry("PageSize", (int)QPrinter::A4, d->pageSize, int);
00329   readConfigNumEntry("MeasureSystem", (int)Metric, d->measureSystem,
00330              MeasureSystem);
00331   readConfigEntry("CalendarSystem", "gregorian", d->calendarType);
00332   delete d->calendar;
00333   d->calendar = 0; // ### HPB Is this the correct place?
00334 
00335   //Grammatical
00336   //Precedence here is l10n / i18n / config file
00337   KSimpleConfig language(locate("locale",
00338                     QString::fromLatin1("%1/entry.desktop")
00339                                 .arg(m_language)), true);
00340   language.setGroup("KCM Locale");
00341 #define read3ConfigBoolEntry(key, default, save) \
00342   save = entry.readBoolEntry(key, default); \
00343   save = language.readBoolEntry(key, save); \
00344   save = config->readBoolEntry(key, save);
00345 
00346   read3ConfigBoolEntry("NounDeclension", false, d->nounDeclension);
00347   read3ConfigBoolEntry("DateMonthNamePossessive", false,
00348                d->dateMonthNamePossessive);
00349 
00350   // end of hack
00351   KGlobal::_locale = lsave;
00352 }
00353 
00354 bool KLocale::setCountry(const QString & country)
00355 {
00356   // Check if the file exists too??
00357   if ( country.isEmpty() )
00358     return false;
00359 
00360   m_country = country;
00361 
00362   d->formatInited = false;
00363 
00364   return true;
00365 }
00366 
00367 QString KLocale::catalogueFileName(const QString & language,
00368                    const KCatalogue & catalog)
00369 {
00370   QString path = QString::fromLatin1("%1/LC_MESSAGES/%2.mo")
00371     .arg( language )
00372     .arg( catalog.name() );
00373 
00374   return locate( "locale", path );
00375 }
00376 
00377 bool KLocale::isLanguageInstalled(const QString & language) const
00378 {
00379   // Do not allow empty languages
00380   if ( language.isEmpty() ) return false;
00381 
00382   bool bRes = true;
00383   if ( language != defaultLanguage() )
00384     for ( QValueList<KCatalogue>::ConstIterator it = d->catalogues.begin();
00385       it != d->catalogues.end() && bRes;
00386       ++it )
00387       {
00388     bRes = !catalogueFileName( language, *it ).isNull();
00389         if ( !bRes )
00390       kdDebug(173) << "message catalog not found: "
00391                << (*it).name() << endl;
00392       }
00393 
00394   return bRes;
00395 }
00396 
00397 bool KLocale::setLanguage(const QString & language)
00398 {
00399   bool bRes = true;
00400 
00401   if (d->first_language.isNull() || language != d->first_language)
00402     bRes = isLanguageInstalled( language );
00403 
00404   if ( bRes )
00405     {
00406       m_language = language;
00407 
00408       // remember our first time - it will be our true love
00409       if (d->first_language.isNull())
00410         d->first_language = language;
00411 
00412       doBindInit();
00413     }
00414 
00415   return bRes;
00416 }
00417 
00418 bool KLocale::setLanguage(const QStringList & languages)
00419 {
00420   QStringList languageList(languages);
00421 
00422   // Remove duplicate entries in reverse so that we
00423   // can keep user's language preference order intact. (DA)
00424   for( QStringList::Iterator it = languageList.fromLast();
00425          it != languageList.begin();
00426          --it )
00427     if ( languageList.contains(*it) > 1 || (*it).isEmpty() )
00428       it = languageList.remove( it );
00429 
00430   bool bRes = false;
00431   for ( QStringList::ConstIterator it = languageList.begin();
00432     it != languageList.end();
00433     ++it )
00434     if ( bRes = setLanguage( *it ) )
00435       break;
00436 
00437   if ( !bRes )
00438     setLanguage(defaultLanguage());
00439 
00440   d->languageList = languageList;
00441   d->langTwoAlpha.clear(); // Flush cache
00442 
00443   return bRes;
00444 }
00445 
00446 void KLocale::splitLocale(const QString & aStr,
00447               QString & language,
00448               QString & country,
00449               QString & chrset)
00450 {
00451   QString str = aStr;
00452 
00453   // just in case, there is another language appended
00454   int f = str.find(':');
00455   if (f >= 0)
00456     str.truncate(f);
00457 
00458   country = QString::null;
00459   chrset = QString::null;
00460   language = QString::null;
00461 
00462   f = str.find('.');
00463   if (f >= 0)
00464     {
00465       chrset = str.mid(f + 1);
00466       str.truncate(f);
00467     }
00468 
00469   f = str.find('_');
00470   if (f >= 0)
00471     {
00472       country = str.mid(f + 1);
00473       str.truncate(f);
00474     }
00475 
00476   language = str;
00477 }
00478 
00479 QString KLocale::language() const
00480 {
00481   return m_language;
00482 }
00483 
00484 QString KLocale::country() const
00485 {
00486   return m_country;
00487 }
00488 
00489 QString KLocale::monthName(int i, bool shortName) const
00490 {
00491   if ( shortName )
00492     switch ( i )
00493       {
00494       case 1:   return translate("January", "Jan");
00495       case 2:   return translate("February", "Feb");
00496       case 3:   return translate("March", "Mar");
00497       case 4:   return translate("April", "Apr");
00498       case 5:   return translate("May short", "May");
00499       case 6:   return translate("June", "Jun");
00500       case 7:   return translate("July", "Jul");
00501       case 8:   return translate("August", "Aug");
00502       case 9:   return translate("September", "Sep");
00503       case 10:  return translate("October", "Oct");
00504       case 11:  return translate("November", "Nov");
00505       case 12:  return translate("December", "Dec");
00506       }
00507   else
00508     switch (i)
00509       {
00510       case 1:   return translate("January");
00511       case 2:   return translate("February");
00512       case 3:   return translate("March");
00513       case 4:   return translate("April");
00514       case 5:   return translate("May long", "May");
00515       case 6:   return translate("June");
00516       case 7:   return translate("July");
00517       case 8:   return translate("August");
00518       case 9:   return translate("September");
00519       case 10:  return translate("October");
00520       case 11:  return translate("November");
00521       case 12:  return translate("December");
00522       }
00523 
00524   return QString::null;
00525 }
00526 
00527 QString KLocale::monthNamePossessive(int i, bool shortName) const
00528 {
00529   if ( shortName )
00530     switch ( i )
00531       {
00532       case 1:   return translate("of January", "of Jan");
00533       case 2:   return translate("of February", "of Feb");
00534       case 3:   return translate("of March", "of Mar");
00535       case 4:   return translate("of April", "of Apr");
00536       case 5:   return translate("of May short", "of May");
00537       case 6:   return translate("of June", "of Jun");
00538       case 7:   return translate("of July", "of Jul");
00539       case 8:   return translate("of August", "of Aug");
00540       case 9:   return translate("of September", "of Sep");
00541       case 10:  return translate("of October", "of Oct");
00542       case 11:  return translate("of November", "of Nov");
00543       case 12:  return translate("of December", "of Dec");
00544       }
00545   else
00546     switch (i)
00547       {
00548       case 1:   return translate("of January");
00549       case 2:   return translate("of February");
00550       case 3:   return translate("of March");
00551       case 4:   return translate("of April");
00552       case 5:   return translate("of May long", "of May");
00553       case 6:   return translate("of June");
00554       case 7:   return translate("of July");
00555       case 8:   return translate("of August");
00556       case 9:   return translate("of September");
00557       case 10:  return translate("of October");
00558       case 11:  return translate("of November");
00559       case 12:  return translate("of December");
00560       }
00561 
00562   return QString::null;
00563 }
00564 
00565 QString KLocale::weekDayName (int i, bool shortName) const
00566 {
00567   if ( shortName )
00568     switch ( i )
00569       {
00570       case 1:  return translate("Monday", "Mon");
00571       case 2:  return translate("Tuesday", "Tue");
00572       case 3:  return translate("Wednesday", "Wed");
00573       case 4:  return translate("Thursday", "Thu");
00574       case 5:  return translate("Friday", "Fri");
00575       case 6:  return translate("Saturday", "Sat");
00576       case 7:  return translate("Sunday", "Sun");
00577       }
00578   else
00579     switch ( i )
00580       {
00581       case 1:  return translate("Monday");
00582       case 2:  return translate("Tuesday");
00583       case 3:  return translate("Wednesday");
00584       case 4:  return translate("Thursday");
00585       case 5:  return translate("Friday");
00586       case 6:  return translate("Saturday");
00587       case 7:  return translate("Sunday");
00588       }
00589 
00590   return QString::null;
00591 }
00592 
00593 void KLocale::insertCatalogue( const QString & catalog )
00594 {
00595   KCatalogue cat( catalog );
00596 
00597   initCatalogue( cat );
00598 
00599   d->catalogues.append( cat );
00600 }
00601 
00602 void KLocale::removeCatalogue(const QString &catalog)
00603 {
00604   for ( QValueList<KCatalogue>::Iterator it = d->catalogues.begin();
00605     it != d->catalogues.end(); )
00606     if ((*it).name() == catalog) {
00607       it = d->catalogues.remove(it);
00608       return;
00609     } else
00610       ++it;
00611 }
00612 
00613 void KLocale::setActiveCatalogue(const QString &catalog)
00614 {
00615   for ( QValueList<KCatalogue>::Iterator it = d->catalogues.begin();
00616     it != d->catalogues.end(); ++it)
00617     if ((*it).name() == catalog) {
00618       KCatalogue save = *it;
00619       d->catalogues.remove(it);
00620       d->catalogues.prepend(save);
00621       return;
00622     }
00623 }
00624 
00625 KLocale::~KLocale()
00626 {
00627   delete d->calendar;
00628   delete d->languages;
00629   delete d;
00630   d = 0L;
00631 }
00632 
00633 QString KLocale::translate_priv(const char *msgid,
00634                 const char *fallback,
00635                 const char **translated) const
00636 {
00637   if (!msgid || !msgid[0])
00638     {
00639       kdWarning() << "KLocale: trying to look up \"\" in catalog. "
00640            << "Fix the program" << endl;
00641       return QString::null;
00642     }
00643 
00644   if ( useDefaultLanguage() )
00645     return QString::fromUtf8( fallback );
00646 
00647   for ( QValueList<KCatalogue>::ConstIterator it = d->catalogues.begin();
00648     it != d->catalogues.end();
00649     ++it )
00650     {
00651       // kdDebug(173) << "translate " << msgid << " " << (*it).name() << " " << (!KGlobal::activeInstance() ? QCString("no instance") : KGlobal::activeInstance()->instanceName()) << endl;
00652       const char * text = (*it).translate( msgid );
00653 
00654       if ( text )
00655     {
00656       // we found it
00657       if (translated)
00658         *translated = text;
00659       return QString::fromUtf8( text );
00660     }
00661     }
00662 
00663   // Always use UTF-8 if the string was not found
00664   return QString::fromUtf8( fallback );
00665 }
00666 
00667 QString KLocale::translate(const char* msgid) const
00668 {
00669   return translate_priv(msgid, msgid);
00670 }
00671 
00672 QString KLocale::translate( const char *index, const char *fallback) const
00673 {
00674   if (!index || !index[0] || !fallback || !fallback[0])
00675     {
00676       kdDebug(173) << "KLocale: trying to look up \"\" in catalog. "
00677            << "Fix the program" << endl;
00678       return QString::null;
00679     }
00680 
00681   if ( useDefaultLanguage() )
00682     return QString::fromUtf8( fallback );
00683 
00684   char *newstring = new char[strlen(index) + strlen(fallback) + 5];
00685   sprintf(newstring, "_: %s\n%s", index, fallback);
00686   // as copying QString is very fast, it looks slower as it is ;/
00687   QString r = translate_priv(newstring, fallback);
00688   delete [] newstring;
00689 
00690   return r;
00691 }
00692 
00693 static QString put_n_in(const QString &orig, unsigned long n)
00694 {
00695   QString ret = orig;
00696   int index = ret.find("%n");
00697   if (index == -1)
00698     return ret;
00699   ret.replace(index, 2, QString::number(n));
00700   return ret;
00701 }
00702 
00703 #define EXPECT_LENGTH(x) \
00704    if (forms.count() != x) { \
00705       kdError() << "translation of \"" << singular << "\" doesn't contain " << x << " different plural forms as expected\n"; \
00706       return QString( "BROKEN TRANSLATION %1" ).arg( singular ); }
00707 
00708 QString KLocale::translate( const char *singular, const char *plural,
00709                             unsigned long n ) const
00710 {
00711   if (!singular || !singular[0] || !plural || !plural[0])
00712     {
00713       kdWarning() << "KLocale: trying to look up \"\" in catalog. "
00714            << "Fix the program" << endl;
00715       return QString::null;
00716     }
00717 
00718   char *newstring = new char[strlen(singular) + strlen(plural) + 6];
00719   sprintf(newstring, "_n: %s\n%s", singular, plural);
00720   // as copying QString is very fast, it looks slower as it is ;/
00721   QString r = translate_priv(newstring, 0);
00722   delete [] newstring;
00723 
00724   if ( r.isEmpty() || useDefaultLanguage() || d->plural_form == -1) {
00725     if ( n == 1 ) {
00726       return put_n_in( QString::fromUtf8( singular ),  n );
00727     } else {
00728       QString tmp = QString::fromUtf8( plural );
00729 #ifndef NDEBUG
00730       if (tmp.find("%n") == -1) {
00731               kdWarning() << "the message for i18n should contain a '%n'! " << plural << endl;
00732       }
00733 #endif
00734       return put_n_in( tmp,  n );
00735     }
00736   }
00737 
00738   QStringList forms = QStringList::split( "\n", r, false );
00739   switch ( d->plural_form ) {
00740   case 0: // NoPlural
00741     EXPECT_LENGTH( 1 );
00742     return put_n_in( forms[0], n);
00743   case 1: // TwoForms
00744     EXPECT_LENGTH( 2 );
00745     if ( n == 1 )
00746       return put_n_in( forms[0], n);
00747     else
00748       return put_n_in( forms[1], n);
00749   case 2: // French
00750     EXPECT_LENGTH( 2 );
00751     if ( n == 1 || n == 0 )
00752       return put_n_in( forms[0], n);
00753     else
00754       return put_n_in( forms[1], n);
00755   case 3: // Gaeilge
00756     EXPECT_LENGTH( 3 );
00757     if ( n == 1 )
00758       return put_n_in( forms[0], n);
00759     else if ( n == 2 )
00760       return put_n_in( forms[1], n);
00761     else
00762       return put_n_in( forms[2], n);
00763   case 4: // Russian, corrected by mok
00764     EXPECT_LENGTH( 3 );
00765     if ( n%10 == 1  &&  n%100 != 11)
00766       return put_n_in( forms[0], n); // odin fail
00767     else if (( n%10 >= 2 && n%10 <=4 ) && (n%100<10 || n%100>20))
00768       return put_n_in( forms[1], n); // dva faila
00769     else
00770       return put_n_in( forms[2], n); // desyat' failov
00771   case 5: // Polish
00772     EXPECT_LENGTH( 3 );
00773     if ( n == 1 )
00774       return put_n_in( forms[0], n);
00775     else if ( n%10 >= 2 && n%10 <=4 && (n%100<10 || n%100>=20) )
00776       return put_n_in( forms[1], n);
00777     else
00778       return put_n_in( forms[2], n);
00779   case 6: // Slovenian
00780     EXPECT_LENGTH( 4 );
00781     if ( n%100 == 1 )
00782       return put_n_in( forms[1], n); // ena datoteka
00783     else if ( n%100 == 2 )
00784       return put_n_in( forms[2], n); // dve datoteki
00785     else if ( n%100 == 3 || n%100 == 4 )
00786       return put_n_in( forms[3], n); // tri datoteke
00787     else
00788       return put_n_in( forms[0], n); // sto datotek
00789   case 7: // Lithuanian
00790     EXPECT_LENGTH( 3 );
00791     if ( n%10 == 0 || (n%100>=11 && n%100<=19) )
00792       return put_n_in( forms[2], n);
00793     else if ( n%10 == 1 )
00794       return put_n_in( forms[0], n);
00795     else
00796       return put_n_in( forms[1], n);
00797   case 8: // Czech
00798     EXPECT_LENGTH( 3 );
00799     if ( n%100 == 1 )
00800       return put_n_in( forms[0], n);
00801     else if (( n%100 >= 2 ) && ( n%100 <= 4 ))
00802       return put_n_in( forms[1], n);
00803     else
00804       return put_n_in( forms[2], n);
00805   case 9: // Slovak
00806     EXPECT_LENGTH( 3 );
00807     if ( n == 1 )
00808       return put_n_in( forms[0], n);
00809     else if (( n >= 2 ) && ( n <= 4 ))
00810       return put_n_in( forms[1], n);
00811     else
00812       return put_n_in( forms[2], n);
00813   case 10: // Maltese
00814     EXPECT_LENGTH( 4 );
00815     if ( n == 1 )
00816       return put_n_in( forms[0], n );
00817     else if ( ( n == 0 ) || ( n%100 > 0 && n%100 <= 10 ) )
00818       return put_n_in( forms[1], n );
00819     else if ( n%100 > 10 && n%100 < 20 )
00820       return put_n_in( forms[2], n );
00821     else
00822       return put_n_in( forms[3], n );
00823   case 11: // Arabic
00824     EXPECT_LENGTH( 4 );
00825     if (n == 1)
00826       return put_n_in(forms[0], n);
00827     else if (n == 2)
00828       return put_n_in(forms[1], n);
00829     else if ( n < 11)
00830       return put_n_in(forms[2], n);
00831     else
00832       return put_n_in(forms[3], n);
00833   case 12: // Balcan
00834      EXPECT_LENGTH( 3 );
00835      if (n != 11 && n % 10 == 1)
00836     return put_n_in(forms[0], n);
00837      else if (n / 10 != 1 && n % 10 >= 2 && n % 10 <= 4)
00838     return put_n_in(forms[1], n);
00839      else
00840     return put_n_in(forms[2], n);
00841   case 13: // Macedonian
00842      EXPECT_LENGTH(3);
00843      if (n % 10 == 1)
00844     return put_n_in(forms[0], n);
00845      else if (n % 10 == 2)
00846     return put_n_in(forms[1], n);
00847      else
00848     return put_n_in(forms[2], n);
00849   }
00850   kdFatal() << "The function should have been returned in another way\n";
00851 
00852   return QString::null;
00853 }
00854 
00855 QString KLocale::translateQt( const char *context, const char *source,
00856                   const char *message) const
00857 {
00858   if (!source || !source[0]) {
00859     kdWarning() << "KLocale: trying to look up \"\" in catalog. "
00860         << "Fix the program" << endl;
00861     return QString::null;
00862   }
00863 
00864   if ( useDefaultLanguage() ) {
00865     return QString::null;
00866   }
00867 
00868   char *newstring = 0;
00869   const char *translation = 0;
00870   QString r;
00871 
00872   if ( message && message[0]) {
00873     char *newstring = new char[strlen(source) + strlen(message) + 5];
00874     sprintf(newstring, "_: %s\n%s", source, message);
00875     const char *translation = 0;
00876     // as copying QString is very fast, it looks slower as it is ;/
00877     r = translate_priv(newstring, source, &translation);
00878     delete [] newstring;
00879     if (translation)
00880       return r;
00881   }
00882 
00883   if ( context && context[0] && message && message[0]) {
00884     newstring = new char[strlen(context) + strlen(message) + 5];
00885     sprintf(newstring, "_: %s\n%s", context, message);
00886     // as copying QString is very fast, it looks slower as it is ;/
00887     r = translate_priv(newstring, source, &translation);
00888     delete [] newstring;
00889     if (translation)
00890       return r;
00891   }
00892 
00893   r = translate_priv(source, source, &translation);
00894   if (translation)
00895     return r;
00896   return QString::null;
00897 }
00898 
00899 bool KLocale::nounDeclension() const
00900 {
00901   doFormatInit();
00902   return d->nounDeclension;
00903 }
00904 
00905 bool KLocale::dateMonthNamePossessive() const
00906 {
00907   doFormatInit();
00908   return d->dateMonthNamePossessive;
00909 }
00910 
00911 int KLocale::weekStartDay() const
00912 {
00913   doFormatInit();
00914   return d->weekStartDay;
00915 }
00916 
00917 bool KLocale::weekStartsMonday() const //deprecated
00918 {
00919   doFormatInit();
00920   return (d->weekStartDay==1);
00921 }
00922 
00923 QString KLocale::decimalSymbol() const
00924 {
00925   doFormatInit();
00926   return m_decimalSymbol;
00927 }
00928 
00929 QString KLocale::thousandsSeparator() const
00930 {
00931   doFormatInit();
00932   return m_thousandsSeparator;
00933 }
00934 
00935 QString KLocale::currencySymbol() const
00936 {
00937   doFormatInit();
00938   return m_currencySymbol;
00939 }
00940 
00941 QString KLocale::monetaryDecimalSymbol() const
00942 {
00943   doFormatInit();
00944   return m_monetaryDecimalSymbol;
00945 }
00946 
00947 QString KLocale::monetaryThousandsSeparator() const
00948 {
00949   doFormatInit();
00950   return m_monetaryThousandsSeparator;
00951 }
00952 
00953 QString KLocale::positiveSign() const
00954 {
00955   doFormatInit();
00956   return m_positiveSign;
00957 }
00958 
00959 QString KLocale::negativeSign() const
00960 {
00961   doFormatInit();
00962   return m_negativeSign;
00963 }
00964 
00965 int KLocale::fracDigits() const
00966 {
00967   doFormatInit();
00968   return m_fracDigits;
00969 }
00970 
00971 bool KLocale::positivePrefixCurrencySymbol() const
00972 {
00973   doFormatInit();
00974   return m_positivePrefixCurrencySymbol;
00975 }
00976 
00977 bool KLocale::negativePrefixCurrencySymbol() const
00978 {
00979   doFormatInit();
00980   return m_negativePrefixCurrencySymbol;
00981 }
00982 
00983 KLocale::SignPosition KLocale::positiveMonetarySignPosition() const
00984 {
00985   doFormatInit();
00986   return m_positiveMonetarySignPosition;
00987 }
00988 
00989 KLocale::SignPosition KLocale::negativeMonetarySignPosition() const
00990 {
00991   doFormatInit();
00992   return m_negativeMonetarySignPosition;
00993 }
00994 
00995 static inline void put_it_in( QChar *buffer, uint& index, const QString &s )
00996 {
00997   for ( uint l = 0; l < s.length(); l++ )
00998     buffer[index++] = s.at( l );
00999 }
01000 
01001 static inline void put_it_in( QChar *buffer, uint& index, int number )
01002 {
01003   buffer[index++] = number / 10 + '0';
01004   buffer[index++] = number % 10 + '0';
01005 }
01006 
01007 QString KLocale::formatMoney(double num,
01008                  const QString & symbol,
01009                  int precision) const
01010 {
01011   // some defaults
01012   QString currency = symbol.isNull()
01013     ? currencySymbol()
01014     : symbol;
01015   if (precision < 0) precision = fracDigits();
01016 
01017   // the number itself
01018   bool neg = num < 0;
01019   QString res = QString::number(neg?-num:num, 'f', precision);
01020   int pos = res.find('.');
01021   if (pos == -1) pos = res.length();
01022   else res.replace(pos, 1, monetaryDecimalSymbol());
01023 
01024   while (0 < (pos -= 3))
01025     res.insert(pos, monetaryThousandsSeparator()); // thousend sep
01026 
01027   // set some variables we need later
01028   int signpos = neg
01029     ? negativeMonetarySignPosition()
01030     : positiveMonetarySignPosition();
01031   QString sign = neg
01032     ? negativeSign()
01033     : positiveSign();
01034 
01035   switch (signpos)
01036     {
01037     case ParensAround:
01038       res.prepend('(');
01039       res.append (')');
01040       break;
01041     case BeforeQuantityMoney:
01042       res.prepend(sign);
01043       break;
01044     case AfterQuantityMoney:
01045       res.append(sign);
01046       break;
01047     case BeforeMoney:
01048       currency.prepend(sign);
01049       break;
01050     case AfterMoney:
01051       currency.append(sign);
01052       break;
01053     }
01054 
01055   if (neg?negativePrefixCurrencySymbol():
01056       positivePrefixCurrencySymbol())
01057     {
01058       res.prepend(' ');
01059       res.prepend(currency);
01060     } else {
01061       res.append (' ');
01062       res.append (currency);
01063     }
01064 
01065   return res;
01066 }
01067 
01068 QString KLocale::formatMoney(const QString &numStr) const
01069 {
01070   return formatMoney(numStr.toDouble());
01071 }
01072 
01073 QString KLocale::formatNumber(double num, int precision) const
01074 {
01075   bool neg = num < 0;
01076   if (precision == -1) precision = 2;
01077   QString res = QString::number(neg?-num:num, 'f', precision);
01078   int pos = res.find('.');
01079   if (pos == -1) pos = res.length();
01080   else res.replace(pos, 1, decimalSymbol());
01081 
01082   while (0 < (pos -= 3))
01083     res.insert(pos, thousandsSeparator()); // thousand sep
01084 
01085   // How can we know where we should put the sign?
01086   res.prepend(neg?negativeSign():positiveSign());
01087 
01088   return res;
01089 }
01090 
01091 QString KLocale::formatLong(long num) const
01092 {
01093   return formatNumber((double)num, 0);
01094 }
01095 
01096 QString KLocale::formatNumber(const QString &numStr) const
01097 {
01098   return formatNumber(numStr.toDouble());
01099 }
01100 
01101 QString KLocale::formatDate(const QDate &pDate, bool shortFormat) const
01102 {
01103   const QString rst = shortFormat?dateFormatShort():dateFormat();
01104 
01105   QString buffer;
01106 
01107   bool escape = false;
01108 
01109   int year = calendar()->year(pDate);
01110   int month = calendar()->month(pDate);
01111 
01112   for ( uint format_index = 0; format_index < rst.length(); ++format_index )
01113     {
01114       if ( !escape )
01115     {
01116       if ( rst.at( format_index ).unicode() == '%' )
01117         escape = true;
01118       else
01119         buffer.append(rst.at(format_index));
01120     }
01121       else
01122     {
01123       switch ( rst.at( format_index ).unicode() )
01124         {
01125         case '%':
01126           buffer.append('%');
01127           break;
01128         case 'Y':
01129           buffer.append(calendar()->yearString(pDate, false));
01130           break;
01131         case 'y':
01132           buffer.append(calendar()->yearString(pDate, true));
01133           break;
01134         case 'n':
01135               buffer.append(calendar()->monthString(pDate, true));
01136           break;
01137         case 'e':
01138               buffer.append(calendar()->dayString(pDate, true));
01139           break;
01140         case 'm':
01141               buffer.append(calendar()->monthString(pDate, false));
01142           break;
01143         case 'b':
01144           if (d->nounDeclension && d->dateMonthNamePossessive)
01145         buffer.append(calendar()->monthNamePossessive(month, year, true));
01146           else
01147         buffer.append(calendar()->monthName(month, year, true));
01148           break;
01149         case 'B':
01150           if (d->nounDeclension && d->dateMonthNamePossessive)
01151         buffer.append(calendar()->monthNamePossessive(month, year, false));
01152           else
01153         buffer.append(calendar()->monthName(month, year, false));
01154           break;
01155         case 'd':
01156               buffer.append(calendar()->dayString(pDate, false));
01157           break;
01158         case 'a':
01159           buffer.append(calendar()->weekDayName(pDate, true));
01160           break;
01161         case 'A':
01162           buffer.append(calendar()->weekDayName(pDate, false));
01163           break;
01164         default:
01165           buffer.append(rst.at(format_index));
01166           break;
01167         }
01168       escape = false;
01169     }
01170     }
01171   return buffer;
01172 }
01173 
01174 void KLocale::setMainCatalogue(const char *catalog)
01175 {
01176   maincatalogue = catalog;
01177 }
01178 
01179 double KLocale::readNumber(const QString &_str, bool * ok) const
01180 {
01181   QString str = _str.stripWhiteSpace();
01182   bool neg = str.find(negativeSign()) == 0;
01183   if (neg)
01184     str.remove( 0, negativeSign().length() );
01185 
01186   /* will hold the scientific notation portion of the number.
01187      Example, with 2.34E+23, exponentialPart == "E+23"
01188   */
01189   QString exponentialPart;
01190   int EPos;
01191 
01192   EPos = str.find('E', 0, false);
01193 
01194   if (EPos != -1)
01195   {
01196     exponentialPart = str.mid(EPos);
01197     str = str.left(EPos);
01198   }
01199 
01200   int pos = str.find(decimalSymbol());
01201   QString major;
01202   QString minor;
01203   if ( pos == -1 )
01204     major = str;
01205   else
01206     {
01207       major = str.left(pos);
01208       minor = str.mid(pos + decimalSymbol().length());
01209     }
01210 
01211   // Remove thousand separators
01212   int thlen = thousandsSeparator().length();
01213   int lastpos = 0;
01214   while ( ( pos = major.find( thousandsSeparator() ) ) > 0 )
01215   {
01216     // e.g. 12,,345,,678,,922 Acceptable positions (from the end) are 5, 10, 15... i.e. (3+thlen)*N
01217     int fromEnd = major.length() - pos;
01218     if ( fromEnd % (3+thlen) != 0 // Needs to be a multiple, otherwise it's an error
01219         || pos - lastpos > 3 // More than 3 digits between two separators -> error
01220         || pos == 0          // Can't start with a separator
01221         || (lastpos>0 && pos-lastpos!=3))   // Must have exactly 3 digits between two separators
01222     {
01223       if (ok) *ok = false;
01224       return 0.0;
01225     }
01226 
01227     lastpos = pos;
01228     major.remove( pos, thlen );
01229   }
01230   if (lastpos>0 && major.length()-lastpos!=3)   // Must have exactly 3 digits after the last separator
01231   {
01232     if (ok) *ok = false;
01233     return 0.0;
01234   }
01235 
01236   QString tot;
01237   if (neg) tot = '-';
01238 
01239   tot += major + '.' + minor + exponentialPart;
01240 
01241   return tot.toDouble(ok);
01242 }
01243 
01244 double KLocale::readMoney(const QString &_str, bool * ok) const
01245 {
01246   QString str = _str.stripWhiteSpace();
01247   bool neg = false;
01248   bool currencyFound = false;
01249   // First try removing currency symbol from either end
01250   int pos = str.find(currencySymbol());
01251   if ( pos == 0 || pos == (int) str.length()-1 )
01252     {
01253       str.remove(pos,currencySymbol().length());
01254       str = str.stripWhiteSpace();
01255       currencyFound = true;
01256     }
01257   if (str.isEmpty())
01258     {
01259       if (ok) *ok = false;
01260       return 0;
01261     }
01262   // Then try removing negative sign from either end
01263   // (with a special case for parenthesis)
01264   if (negativeMonetarySignPosition() == ParensAround)
01265     {
01266       if (str[0] == '(' && str[str.length()-1] == ')')
01267         {
01268       neg = true;
01269       str.remove(str.length()-1,1);
01270       str.remove(0,1);
01271         }
01272     }
01273   else
01274     {
01275       int i1 = str.find(negativeSign());
01276       if ( i1 == 0 || i1 == (int) str.length()-1 )
01277         {
01278       neg = true;
01279       str.remove(i1,negativeSign().length());
01280         }
01281     }
01282   if (neg) str = str.stripWhiteSpace();
01283 
01284   // Finally try again for the currency symbol, if we didn't find
01285   // it already (because of the negative sign being in the way).
01286   if ( !currencyFound )
01287     {
01288       pos = str.find(currencySymbol());
01289       if ( pos == 0 || pos == (int) str.length()-1 )
01290         {
01291       str.remove(pos,currencySymbol().length());
01292       str = str.stripWhiteSpace();
01293         }
01294     }
01295 
01296   // And parse the rest as a number
01297   pos = str.find(monetaryDecimalSymbol());
01298   QString major;
01299   QString minior;
01300   if (pos == -1)
01301     major = str;
01302   else
01303     {
01304       major = str.left(pos);
01305       minior = str.mid(pos + monetaryDecimalSymbol().length());
01306     }
01307 
01308   // Remove thousand separators
01309   int thlen = monetaryThousandsSeparator().length();
01310   int lastpos = 0;
01311   while ( ( pos = major.find( monetaryThousandsSeparator() ) ) > 0 )
01312   {
01313     // e.g. 12,,345,,678,,922 Acceptable positions (from the end) are 5, 10, 15... i.e. (3+thlen)*N
01314     int fromEnd = major.length() - pos;
01315     if ( fromEnd % (3+thlen) != 0 // Needs to be a multiple, otherwise it's an error
01316         || pos - lastpos > 3 // More than 3 digits between two separators -> error
01317         || pos == 0          // Can't start with a separator
01318         || (lastpos>0 && pos-lastpos!=3))   // Must have exactly 3 digits between two separators
01319     {
01320       if (ok) *ok = false;
01321       return 0.0;
01322     }
01323     lastpos = pos;
01324     major.remove( pos, thlen );
01325   }
01326   if (lastpos>0 && major.length()-lastpos!=3)   // Must have exactly 3 digits after the last separator
01327   {
01328     if (ok) *ok = false;
01329     return 0.0;
01330   }
01331 
01332   QString tot;
01333   if (neg) tot = '-';
01334   tot += major + '.' + minior;
01335   return tot.toDouble(ok);
01336 }
01337 
01344 static int readInt(const QString &str, uint &pos)
01345 {
01346   if (!str.at(pos).isDigit()) return -1;
01347   int result = 0;
01348   for (; str.length() > pos && str.at(pos).isDigit(); pos++)
01349     {
01350       result *= 10;
01351       result += str.at(pos).digitValue();
01352     }
01353 
01354   return result;
01355 }
01356 
01357 QDate KLocale::readDate(const QString &intstr, bool* ok) const
01358 {
01359   QDate date;
01360   date = readDate(intstr, ShortFormat, ok);
01361   if (date.isValid()) return date;
01362   return readDate(intstr, NormalFormat, ok);
01363 }
01364 
01365 QDate KLocale::readDate(const QString &intstr, ReadDateFlags flags, bool* ok) const
01366 {
01367   QString fmt = ((flags & ShortFormat) ? dateFormatShort() : dateFormat()).simplifyWhiteSpace();
01368   return readDate( intstr, fmt, ok );
01369 }
01370 
01371 QDate KLocale::readDate(const QString &intstr, const QString &fmt, bool* ok) const
01372 {
01373   //kdDebug() << "KLocale::readDate intstr=" << intstr << " fmt=" << fmt << endl;
01374   QString str = intstr.simplifyWhiteSpace().lower