The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]

Каталог документации / Раздел "Программирование, языки" / Оглавление документа

15.2. Разработка приложений, подготовленных к переводу.

Если необходимо предусмотреть возможность перевода приложения на разные языки, следует соблюдать следующие положения:

Эти условия не являются обязательными для приложений, которые никогда не будут переведены на другие языки. Однако, использование функции tr() не настолько обременительно, чтобы отказываться от нее. К тому же, тем самым вы оставляете открытой возможность локализации приложения в будущем.

Функция tr() -- статическая, она определена в классе QObject и перекрывается в каждом классе-потомке, который включает в свое определение макрос Q_OBJECT. Она возвращает перевод заданной строки, если он существует, или оригинальную версию строки -- в противном случае.

Для подготовки файла перевода необходимо запустить утилиту Qt -- lupdate. Она извлечет из исходного текста программы все строки, которые передаются функции tr() и создаст файл перевода. Этот файл может быть передан переводчику, который добавит в него перевод для каждой из строк. Более подробно процесс перевода описан в разделе Перевод существующих приложений.

Функция tr() имеет следующий синтаксис вызова:

  Context::tr(sourceText, comment)
      
Часть имени Context -- это имя класса, производного от QObject. Если функция вызывается в контексте класса, то указание имени класса не обязательно. sourceText -- это строка символов, которая должна быть переведена. comment -- не обязательный аргумент, может использоваться для предоставления дополнительной информации переводчику.

Еще один пример:

BlueWidget::BlueWidget(QWidget *parent, const char *name) 
    : QWidget(parent, name) 
{ 
  QString str1 = tr("Legal"); 
  QString str2 = BlueWidget::tr("Legal"); 
  QString str3 = YellowDialog::tr("Legal"); 
  QString str4 = YellowDialog::tr("Legal", "US paper size"); 
}
      
Первые два вызова производятся в контексте класса BlueWidget, последние два -- YellowDialog. Все четыре вызова получают строку "Legal" в качестве исходной, кроме того, последний из них имеет дополнительный комментарий, который поможет переводчику понять смысл исходной строки.

Перевод строк в разных контекстах выполняется независимо друг от друга. Переводчики обычно учитывают контекст в своей работе, часто выполняя пробные запуски приложения и оценивая качество перевода.

При вызове tr() из глобальных функций, необходимо явно указывать контекст, в качестве которого может использоваться любой класс, наследник от QObject. Если в приложении нет ничего подходящего, всегда можно прибегнуть к услугам самого QObject, например:

int main(int argc, char *argv[]) 
{ 
  QApplication app(argc, argv); 
  ... 
  QPushButton button(QObject::tr("Hello Qt!"), 0); 
  app.setMainWidget(&button); 
  button.show(); 
  return app.exec(); 
}
      
Очень часто полезной оказывается следующая методика, которая может быть применена к переводу названия приложения: вместо того, чтобы всякий раз набивать строки с именем приложения и вынуждать переводчика переводить их для каждого из контекстов, в котором они используются, более удобным будет определить его в виде макроса APPNAME, поместить макрос в заголовочный файл и использовать его по мере необходимости:
#define APPNAME MainWindow::tr("OpenDrawer 2D")
      
До сих пор, в качестве контекста мы рассматривали имя класса. Это довольно удобно, поскольку в большинстве случаев мы можем не указывать контекст перевода явно, при вызове функции tr(). Но более универсальный способ подготовки строк к переводу состоит в использовании функции QApplication:: translate(), которая принимает три аргумента: контекст, исходный текст и необязательный комментарий. Например, еще один способ определения макроса APPNAME:
#define APPNAME qApp->translate("Global Stuff", "OpenDrawer 2D")
      
На этот раз текст помещен в контекст "Global Stuff".

Функции tr() и translate() имеют двойное назначение: они служат маркерами для утилиты lupdate и в то же самое время -- это обычные функции C++, которые выполняют перевод текста. Такая двойственность накладывает некоторые ограничения на то, как записывается исходный код. Например, следующий отрывок не будет выполнять перевод строки на другой язык:

  // НЕВЕРНО
  const char *appName = "OpenDrawer 2D"; 
  QString translated = tr(appName);
      
Проблема состоит в том, что lupdate не сможет отыскать строку "OpenDrawer 2D", поскольку она явно не передается функции tr(). Эта проблема очень часто проявляется при работе с динамическими строками:
  // НЕВЕРНО
  statusBar()->message(tr("Host " + hostName + " found"));
      
Здесь строка изменяется динамически, в зависимости от значения переменной hostName, таким образом мы не можем требовать от tr() корректного перевода.

Как одно из решений проблемы -- используйте QString::arg():

  statusBar()->message(tr("Host %1 found").arg(hostName));
      
Остановимся в этом месте чуть подробнее: функции tr() передается строка символов "Host %1 found". Допустим, что приложение загрузило файл с русским переводом, тогда функция tr() должна вернуть примерно такую строку: "Обнаружен узел сети %1". После этого аргумент '%1' замещается содержимым переменной hostName. В результате мы получаем вполне корректный перевод сообщения, которое демонстрируется русскоговорящему пользователю.

В случае, когда необходимо записать перевод строки в переменную, следует использовать макрос QT_TR_NOOP(). Чаще всего этот прием используется при создании статических массивов строк, например:

void OrderForm::init() 
{ 
  static const char * const flowers[] = { 
      QT_TR_NOOP("Medium Stem Pink Roses"), 
      QT_TR_NOOP("One Dozen Boxed Roses"), 
      QT_TR_NOOP("Calypso Orchid"), 
      QT_TR_NOOP("Dried Red Rose Bouquet"), 
      QT_TR_NOOP("Mixed Peonies Bouquet"), 
      0 
  };
  int i = 0; 
  while (flowers[i]) { 
    comboBox->insertItem(tr(flowers[i])); 
    ++i; 
  } 
}
      
Макрос QT_TR_NOOP() фактически ничего не делает, но он служит маркером для lupdate. Строки, передаваемые этому макросу попадут в файл перевода и затем tr() переведет содержимое переменной обычным образом. Как видите, даже не смотря на то, что функции tr() передается не текст, а переменная, перевод будет выполнен корректно.

Есть еще один макрос -- QT_TRANSLATE_NOOP(), который похож на QT_TR_NOOP(), только в отличие от последнего, ему можно задать контекст перевода. Этот макрос найдет применение, когда необходимо инициализировать переменные за пределами класса:

static const char * const flowers[] = { 
    QT_TRANSLATE_NOOP("OrderForm", "Medium Stem Pink Roses"), 
    QT_TRANSLATE_NOOP("OrderForm", "One Dozen Boxed Roses"), 
    QT_TRANSLATE_NOOP("OrderForm", "Calypso Orchid"), 
    QT_TRANSLATE_NOOP("OrderForm", "Dried Red Rose Bouquet"), 
    QT_TRANSLATE_NOOP("OrderForm", "Mixed Peonies Bouquet"), 
    0 
};
      
причем контекст должен совпадать с контекстом вызова функции tr(), которая будет выполнять перевод этих строк.

При использовании tr() в приложении не так уж и сложно забыть заключить какие нибудь строки в вызов этой функции, особенно если вы еще новичок. Эти досадные промахи будут проявляться в локализованных приложениях в виде непереведенных сообщений или надписей, вызывая чувство недовольства у пользователя. Чтобы избежать этой проблемы, мы можем запретить неявное преобразование из const char * в QString, определив символ препроцессора QT_NO_CAST_ASCII, перед директивой подключения заголовочного файла <qstring.h>. Самый простой способ определить этот символ -- поместить в файл .pro следующую строку:

DEFINES += QT_NO_CAST_ASCII
      
В результате, каждая строка, которая не пропускается через вызов tr() или QString:: fromAscii() (в зависимости от того, должна строка подвергаться переводу или нет), будет вызывать ошибку времени компиляции.

После того, как все строки будут "завернуты" в вызовы tr(), остается соблюсти еще одно важное условие -- загрузить на запуске файл с переводом. Обычно это делается в функции main(). Например, следующий код загрузит файл с переводом, с учетом региональных настроек пользователя:

int main(int argc, char *argv[]) 
{ 
  QApplication app(argc, argv);
  
  QTranslator appTranslator; 
  appTranslator.load(QString("app_") + QTextCodec::locale(), 
                     qApp->applicationDirPath()); 
  app.installTranslator(&appTranslator); 
  ... 
  return app.exec(); 
}
      
Функция QTextCodec::locale() возвращает строку -- имя локали пользователя, запустившего приложение. Локаль может быть определена более или менее точно, например, ru определяет русскую локаль, ru_RU -- русскую локаль для России, ru_RU.KOI8-R -- русскую локаль для России, с кодировкой символов KOI8-R.

Предположим, что приложение получило строку с именем локали -- ru_RU.KOI8-R, тогда load() попытается сначала загрузить файл app_ru_RU.KOI8-R.qm. Если этот файл отсутствует, то load() попытается загрузить файл app_ru_RU.qm, затем app_ru.qm и наконец app.qm. Обычно, в таких случаях достаточно создать один файл, с именем app_ru.qm. Однако, если перевод предполагает более точный учет региональных настроек, как например в случае fr_FR (французский язык для Франции) и fr_CA (французский язык для Канады), то может потребоваться создать отдельные файлы с переводом для каждого из регионов.

Второй аргумент функции load() -- это каталог, где находится файл с переводом. Компания Trolltech предоставляет файлы с французским и немецким переводами Qt в каталоге translations. (Переводы на некоторые другие языки так же могут поставляться вместе с библиотекой, но все они выполняются командами добровольцев и официально не поддерживаются.) Так же должен подгружаться библиотечный файл с переводом:

  QTranslator qtTranslator; 
  qtTranslator.load(QString("qt_") + QTextCodec::locale(), 
                    qApp->applicationDirPath()); 
  app.installTranslator(&qtTranslator);
      
Экземпляр класса QTranslator может хранить только один файл с переводом, поэтому следует использовать различные QTranslator. Но это не является большой проблемой, так как мы можем создать столько экземпляров класса QTranslator, сколько потребуется. Все они будут использоваться приложением при поиске перевода.

В некоторых языках, таких как арабский и иврит, строки пишутся справа-налево. В этих случаях приложению необходимо сообщить о порядке вывода строк вызовом QApplication::setReverseLayout(true). Для таких языков, файл перевода должен содержать специальный маркер -- "LTR", который обеспечивает корректный вывод переведенных строк.

Для пользователей программы может оказаться более удобным вариант, когда файлы перевода внедряются в тело исполняемого файла программы. Мало того, что этот прием уменьшает количество файлов, которые придется распространять вместе сприложением, но это так же сведет к минимуму риск случайной потери файлов с переводами. Для реализации этой возможности, в составе Qt распространяется утилита qembed, которая преобразует файлы с переводами в массивы C++, которые могут передаваться функции QTranslator::load().

Мы описали все, что необходимо сделать, чтобы подготовить приложение к интернационализации. Но язык и направление письма это еще не все, что отличает страны и культуры. Интернационализированная программа должна принимать во внимание формат представления даты, времени, национальной валюты, чисел и порядок сортировки строк. Для этого в Qt 3.2 не существует никаких специальных функций, но мы можем использовать стандартные функции setlocale() и localeconv(). [1]

Некоторые функции и классы Qt адаптируют свое поведение под настройки локали:

Наконец, вместе с переводом, приложение может использовать разные наборы иконок для разных языков. Например, для языков, в которых письмо осуществляется справа-налево, в web-браузере логичнее было бы поменять местами кнопки "Назад" и "Вперед". Сделать это можно следующим образом:
  if (QApplication::reverseLayout()) { 
    backAct->setIconSet(forwardIcon); 
    forwardAct->setIconSet(backIcon); 
  } else { 
    backAct->setIconSet(backIcon); 
    forwardAct->setIconSet(forwardIcon); 
  }
      
Иконки, изображение на которых соответствует алфавитным символам, очень часто должны быть адаптированы, в соответствии с конкретными языковыми настройками. Например, в текстовых процессорах, иконка с изображением символа "I" (что означает "Italic" -- Курсив) должна быть заменена на "C" для Испании (Cursivo) или на "K" -- для России (Курсив). Самый простой способ:
  if (tr("Italic")[0] == 'C') { 
    italicAct->setIconSet(iconC); 
  } else if (tr("Italic")[0] == 'K') { 
    italicAct->setIconSet(iconK); 
  } else { 
    italicAct->setIconSet(iconI); 
  }
      

Примечания

[1]

Вероятно, в состав Qt 3.3 будет включен класс QLocale, который будет обслуживать представление числовых форматов.




Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2024 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру