vdr/i18n.c
Klaus Schmidinger 46c882c04d Version 1.5.9
- Fixed handling locale directories with a large number of entries (thanks to
  Anssi Hannula).
- Updated Turkish language texts (thanks to Oktay Yolgeçen).
- Fixed stripping the context in I18nTranslate() (reported by Christian
  Wieninger).
- Fixed detecting whether a particular locale is actually supported.
- Added a note about LANG having to be set to a valid locale in INSTALL
  (suggested by Matthias Fechner).
- Fixed some compiler warnings with gcc-4.2.0 (thanks to Matthias Schwarzott).
- Fixed setting the locale file name in i18n-to-gettext.pl (thanks to Matthias
  Schwarzott).
- Changed the default for LOCDIR in Makefile and Make.config.template to
  "./locale", so that internationalization works by default when running VDR
  from within its source directory (suggested by Anssi Hannula).
- Added the new i18n macro trVDR(), which can be used by plugins to mark
  texts they want to reuse from VDR's core translations (suggested by Matthias
  Becker).
- VDR now uses the default configuration directory as defined in the CONFDIR
  varable in the Makefile (thanks to Thomas Schmidt).
- The SVDRP command LSTC can now list the channels with group separators if the
  option ':groups' is given (thanks to Andreas Mair).
- Added a missing error report to cCuttingThread::Action() (thanks to Udo
  Richter).
- There can now be more than one OSD at the same time. At any given time,
  however, only one of them can be active (and thus visible). This is to
  allow displaying things like subtitles in an easy way. A cOsd therefore
  now has a "Level", and only the OSD with the smallest level will be
  displayed. The level 0 OSD is special, and there can only be one with
  this level. If there is more than one OSD with a particular level, only
  the one that was created first will be displayed.
  Plugins that provide an OSD need to adjust their cOsdProvider::CreateOsd()
  function to hand through the Level.
- Fixed checking for ttDolbyLast in cDevice::SetCurrentAudioTrack() (thanks
  to Marco Schlüßler).
2007-08-26 18:00:00 +02:00

299 lines
8.7 KiB
C

/*
* i18n.c: Internationalization
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: i18n.c 1.317 2007/08/24 14:03:47 kls Exp $
*
*
*/
/*
* In case an English phrase is used in more than one context (and might need
* different translations in other languages) it can be preceded with an
* arbitrary string to describe its context, separated from the actual phrase
* by a '$' character (see for instance "Button$Stop" vs. "Stop").
* Of course this means that no English phrase may contain the '$' character!
* If this should ever become necessary, the existing '$' would have to be
* replaced with something different...
*/
#include "i18n.h"
#include <ctype.h>
#include <libintl.h>
#include <locale.h>
#include <unistd.h>
#include "tools.h"
// TRANSLATORS: The name of the language, as written natively
const char *LanguageName = trNOOP("LanguageName$English");
// TRANSLATORS: The 3-letter code of the language
const char *LanguageCode = trNOOP("LanguageCode$eng");
// List of known language codes with aliases.
// Actually we could list all codes from http://www.loc.gov/standards/iso639-2
// here, but that would be several hundreds - and for most of them it's unlikely
// they're ever going to be used...
const char *LanguageCodeList[] = {
"eng,dos",
"deu,ger",
"slv,slo",
"ita",
"dut,nla,nld",
"por",
"fra,fre",
"nor",
"fin,smi",
"pol",
"esl,spa",
"ell,gre",
"sve,swe",
"rom,rum",
"hun",
"cat,cln",
"rus",
"hrv",
"est",
"dan",
"cze,ces",
"tur",
NULL
};
static const char *I18nLocaleDir = LOCDIR;
static cStringList LanguageLocales;
static cStringList LanguageNames;
static cStringList LanguageCodes;
static int NumLocales = 1;
static int CurrentLanguage = 0;
static bool ContainsCode(const char *Codes, const char *Code)
{
while (*Codes) {
int l = 0;
for ( ; l < 3 && Code[l]; l++) {
if (Codes[l] != tolower(Code[l]))
break;
}
if (l == 3)
return true;
Codes++;
}
return false;
}
static const char *SkipContext(const char *s)
{
const char *p = strchr(s, '$');
return p ? p + 1 : s;
}
static void SetEnvLanguage(const char *Locale)
{
setenv("LANGUAGE", Locale, 1);
extern int _nl_msg_cat_cntr;
++_nl_msg_cat_cntr;
}
void I18nInitialize(void)
{
LanguageLocales.Append(strdup(I18N_DEFAULT_LOCALE));
LanguageNames.Append(strdup(SkipContext(LanguageName)));
LanguageCodes.Append(strdup(LanguageCodeList[0]));
textdomain("vdr");
bindtextdomain("vdr", I18nLocaleDir);
cFileNameList Locales(I18nLocaleDir, true);
if (Locales.Size() > 0) {
char *OldLocale = strdup(setlocale(LC_MESSAGES, NULL));
for (int i = 0; i < Locales.Size(); i++) {
cString FileName = cString::sprintf("%s/%s/LC_MESSAGES/vdr.mo", I18nLocaleDir, Locales[i]);
if (access(FileName, F_OK) == 0) { // found a locale with VDR texts
if (NumLocales < I18N_MAX_LANGUAGES - 1) {
SetEnvLanguage(Locales[i]);
const char *TranslatedLanguageName = gettext(LanguageName);
if (TranslatedLanguageName != LanguageName) {
NumLocales++;
if (strstr(OldLocale, Locales[i]) == OldLocale)
CurrentLanguage = LanguageLocales.Size();
LanguageLocales.Append(strdup(Locales[i]));
LanguageNames.Append(strdup(TranslatedLanguageName));
const char *Code = gettext(LanguageCode);
for (const char **lc = LanguageCodeList; *lc; lc++) {
if (ContainsCode(*lc, Code)) {
Code = *lc;
break;
}
}
LanguageCodes.Append(strdup(Code));
}
}
else {
esyslog("ERROR: too many locales - increase I18N_MAX_LANGUAGES!");
break;
}
}
}
SetEnvLanguage(LanguageLocales[CurrentLanguage]);
free(OldLocale);
dsyslog("found %d locales in %s", NumLocales - 1, I18nLocaleDir);
}
// Prepare any known language codes for which there was no locale:
for (const char **lc = LanguageCodeList; *lc; lc++) {
bool Found = false;
for (int i = 0; i < LanguageCodes.Size(); i++) {
if (strcmp(*lc, LanguageCodes[i]) == 0) {
Found = true;
break;
}
}
if (!Found) {
dsyslog("no locale for language code '%s'", *lc);
LanguageLocales.Append(strdup(I18N_DEFAULT_LOCALE));
LanguageNames.Append(strdup(*lc));
LanguageCodes.Append(strdup(*lc));
}
}
}
void I18nRegister(const char *Plugin)
{
cString Domain = cString::sprintf("vdr-%s", Plugin);
bindtextdomain(Domain, I18nLocaleDir);
}
void I18nSetLocale(const char *Locale)
{
if (Locale && *Locale) {
int i = LanguageLocales.Find(Locale);
if (i >= 0) {
CurrentLanguage = i;
SetEnvLanguage(Locale);
}
else
dsyslog("unknown locale: '%s'", Locale);
}
}
int I18nCurrentLanguage(void)
{
return CurrentLanguage;
}
void I18nSetLanguage(int Language)
{
if (Language < LanguageNames.Size()) {
CurrentLanguage = Language;
I18nSetLocale(I18nLocale(CurrentLanguage));
}
}
int I18nNumLanguagesWithLocale(void)
{
return NumLocales;
}
const cStringList *I18nLanguages(void)
{
return &LanguageNames;
}
const char *I18nTranslate(const char *s, const char *Plugin)
{
if (!s)
return s;
if (CurrentLanguage) {
const char *t = Plugin ? dgettext(Plugin, s) : gettext(s);
if (t != s)
return t;
}
return SkipContext(s);
}
const char *I18nLocale(int Language)
{
return 0 <= Language && Language < LanguageLocales.Size() ? LanguageLocales[Language] : NULL;
}
const char *I18nLanguageCode(int Language)
{
return 0 <= Language && Language < LanguageCodes.Size() ? LanguageCodes[Language] : NULL;
}
int I18nLanguageIndex(const char *Code)
{
for (int i = 0; i < LanguageCodes.Size(); i++) {
if (ContainsCode(LanguageCodes[i], Code))
return i;
}
//dsyslog("unknown language code: '%s'", Code);
return -1;
}
const char *I18nNormalizeLanguageCode(const char *Code)
{
for (int i = 0; i < 3; i++) {
if (Code[i]) {
// ETSI EN 300 468 defines language codes as consisting of three letters
// according to ISO 639-2. This means that they are supposed to always consist
// of exactly three letters in the range a-z - no digits, UTF-8 or other
// funny characters. However, some broadcasters apparently don't have a
// copy of the DVB standard (or they do, but are perhaps unable to read it),
// so they put all sorts of non-standard stuff into the language codes,
// like nonsense as "2ch" or "A 1" (yes, they even go as far as using
// blanks!). Such things should go into the description of the EPG event's
// ComponentDescriptor.
// So, as a workaround for this broadcaster stupidity, let's ignore
// language codes with unprintable characters...
if (!isprint(Code[i])) {
//dsyslog("invalid language code: '%s'", Code);
return "???";
}
// ...and replace blanks with underlines (ok, this breaks the 'const'
// of the Code parameter - but hey, it's them who started this):
if (Code[i] == ' ')
*((char *)&Code[i]) = '_';
}
else
break;
}
int n = I18nLanguageIndex(Code);
return n >= 0 ? I18nLanguageCode(n) : Code;
}
bool I18nIsPreferredLanguage(int *PreferredLanguages, const char *LanguageCode, int &OldPreference, int *Position)
{
int pos = 1;
bool found = false;
while (LanguageCode) {
int LanguageIndex = I18nLanguageIndex(LanguageCode);
for (int i = 0; i < LanguageCodes.Size(); i++) {
if (PreferredLanguages[i] < 0)
break; // the language is not a preferred one
if (PreferredLanguages[i] == LanguageIndex) {
if (OldPreference < 0 || i < OldPreference) {
OldPreference = i;
if (Position)
*Position = pos;
found = true;
break;
}
}
}
if ((LanguageCode = strchr(LanguageCode, '+')) != NULL) {
LanguageCode++;
pos++;
}
else if (pos == 1 && Position)
*Position = 0;
}
if (OldPreference < 0) {
OldPreference = LanguageCodes.Size(); // higher than the maximum possible value
return true; // if we don't find a preferred one, we take the first one
}
return found;
}