среда, 14 августа 2013 г.

Ещё немного о переводах в Qt: то, что могло бы быть предусмотрено разработчиками

Система переводов в Qt, на мой взгляд, является одной из наименее проработанных частей этого фреймворка. Да, имеются все необходимые возможности, но не хватает некоторых мелочей. Вместо тысячи слов: QT_TRANSLATE_NOOP3

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

Не пускаясь в абстрактные рассуждения и высокие материи, рассмотрим вполне реальный пример. Допустим, нам нужно показывать пользователю определённые сообщения в ответ на ввод соответствующих команд (например, в справочной системе консольного приложения). Конечно, если это отдельное приложение, то можно просто захардкодить нужные строки, обёрнутые в функцию tr. Но если данная возможность реализована в библиотеке и используется несколькими разными приложениями, то такой вариант не пройдёт. Можно, конечно, использовать виртуальные методы, но такой подход, на мой взгляд, излишне громоздкий. Гораздо более естественно создать словарь, в котором команде будет сопоставляться строка, а затем пополнять этот словарь. Вот как это может выглядеть:
 QMap<QString, QString> map;  
 map.insert("command", "This is a command. It does nothing.");  
Отлично, осталось сделать строку переводимой. Ясно, что средствами Qt тут ничего хорошего добиться не удастся. Поэтому предлагаю использовать вот такой простенький класс:
 class Translation  
 {  
 public:  
   static Translation translate(const char *context, const char *sourceText, const char *disambiguation = 0, int n = -1)  
 {  
   if (n < 0)  
     n = -1;  
   Translation t;  
   mcontext = context;  
   msourceText = sourceText;  
   mdisambiguation = disambiguation;  
   mn = n;  
   return t;  
 }  
 public:  
   explicit Translation()  
   {  
     mn = -1;  
   }  
 public:  
   QString Translation::translate() const  
   {  
     QCoreApplication::translate(mcontext.toUtf8().constData(), msourceText.toUtf8().constData(), mdisambiguation.toUtf8().constData(), mn);  
   }  
 private:  
   QString mcontext;  
   QString msourceText;  
   QString mdisambiguation;  
   int mn;  
 };  
Как известно, если обернуть строку в функцию translate, то она будет помечена к переводу. Не важно, в каком пространстве имён находится эта функция. В классе Translation использовано данное обстоятельство. Экземпляр класса создаётся посредством вызова статического метода translate. То есть этот класс — просто обёртка, хранящая исходную строку, которую в любом месте программы можно перевести с использованием QCoreApplication::translate. Конструктор копирования и различные операторы в коде отсутствуют, но подразумеваются.

Пример использования:
 //создаём словарь и пополняем его  
 QMap<QString, Translation> map;  
 map.insert("command", Translation::translate("Some context", "This is a command. It does nothing.", "Some comment"));  
 //выводим соответствующую строку при вводе команды  
 QTextStream in(stdin, QIODevice::ReadOnly);  
 QString command = in.readLine();  
 QTextStream out(stdout, QIODevice::WriteOnly);  
 out << map.value(command).translate();  
На выходе получим переведённую строку. Казалось бы, мелочь, а весьма удобно и естественно по сравнению с этим ужасным макросом QT_TRANSLATE_NOOP3 и по сравнению с использованием виртуальных методов. Есть, конечно, некоторый проигрыш в скорости и объёме памяти, но... как говорится, не смешите мои тапочки. :)