Как стать автором
Обновить

Комментарии 23

Ради справедливости,
Неудобство 1: хранение переводов
проистекает от плохой архитектуры. Если команды представлять самостоятельными объектами, а не неявным набором глобальных хендлеров и отображений, то у вас будет красиво реализованный вывод документации на текущем языке.

Если же вам реально нужен именно отложенный перевод, то да, обёрточка, которая явно показывает, что это строка, которая будет переведена на текущий язык при использовании. Не вижу причин расстраиваться из-за того, что ребята из Qt не написали её за вас.

А вот
Неудобство 2: динамический перевод при изменении языка
реально присутствует. Лечится только ручным прописыванием того, что надо обновлять, в каком-нибудь обработчике changeEvent() — или в каждом виджете отдельно, или собирая эту информацию в глобальном мониторе вроде вашего DynamicTranslator.
Хотя, в общем-то, это одна и та же проблема: энергичность переводов.

QObject::tr() и QCoreApplication::translate() выдают сразу QString, хотя по идее хотелось бы, чтобы они возвращали какой-нибудь QLanguageDependentString, который откладывает перевод до последнего момента и автомагически запоминает, кто запросил переведённую строку, чтобы в случае изменения языка отправить ему обновление.

Но, мне кажется, это нереализуемо в текущих условиях, так как все хотят QString, а над ним обёртку не сделаешь.
Написать свой QLanguageDependentString, унаследовав его от QString:
-при записи в него строки он переводит ее на текущий язык, и далее ведет себя как QString, содержащая перевод.
-при всяком обращении проверяет, что сейчас установлен тот же язык, что и у имеющегося перевода. Если язык не совпал, переводим строку заново.

А вообще да, у автора архитектурная проблема с расщеплением модель-представление, отсюда и костыль родился.
Это сработает только для самописных классов. Ну, можно еще исходники Qt под этот QLanguageDependentString переделать, но передавать придется по указателю, ибо, если я ничего не путаю, будет как-то так:
Скрытый текст
#include <iostream>

class Base
{
public:
    virtual int method() { return 1; }
};

class Inherited : public Base
{
public:
    int method() { return 2; }
};

int main(int, char **)
{
    Inherited *px = new Inherited;
    Base *py = px;
    std::cout << py->method(); //2

    //но

    Inherited x;
    Base y = x;
    std::cout << y.method(); //1
    return 0;
}

Зачем так-то? Нужно унаследоваться от QString и перезагрузить ему все методы своими. Не трогая исходники родной QString. Напоминаю, что наследование — это не способ избавления от повторного использования кода, а архитектурный прием, позволяющий объяснить классу, что мы передаем ему сущность, с которой он знаком и умеет работать.

Внутри нашего класса мы будем, понятное дело, держать настоящий QString с переведенной строкой, которому и делегируем все обращения к своим методам.
Как тогда этот наш класс передать, скажем, в QWidget::setWindowTitle? Приведите, пожалуйста, пример.
class QLanguageDependentString : public QString
{
//...
};

widget->setWindowTitle(QLanguageDependentString("Cool widget")); //Это не сработает по упомянутой выше причине
setWindowTitle(нашКласс.дайCюдаПереведеннуюСтроку())

Это действие мы будем проделывать, когда строка из нашей зоны ответственности отправляется в Qt.

Да, при смене перевода придется перевыставить заголовки всем окнам. И вообще, перестроить весь интерфейс. А если язык выбрали арабский? Кошмар, там еще и раскладку элементов по форме быть может придется менять.

Вы пытаетесь бороться с такой проблемой — вы получаете переводы, эти переводы куда-то сохраняете. В вашей модели появляются куски представления.

После этого пользователь меняет язык. Это означает, на самом деле, что все что вы насохраняли надо уничтожить и перегенерировать заново. Либо выполнять перевод в последний момент — перед отдачей строк в интерфейс.

Второе на самом деле означает, что придется всю работу со строками реализовывать в «ленивой» манере — нужно будет запоминать все действия, которые нужно сделать над строкой, а затем, когда надо отдать значения в интерфейс, эти действия проделывать.

Гораздо проще при смене языка перестроить все, что от этого языка зависело.
setWindowTitle(нашКласс.дайCюдаПереведеннуюСтроку())

Так «нашКласс» это и есть мой Translation, разве нет? Хранит в себе строку, при необходимости возвращает в переведенном виде.
Гораздо проще при смене языка перестроить все, что от этого языка зависело.

Не всегда. Зачем мне, скажем, в приложении на 200 строк городить абстракций еще на 200 строк ради перевода пары кнопок? В больших энтерпрайз-проектах это, безусловно, оправдано, но не в маленьких утилитах.
Скажите, что вы будете делать, если у вас параметры в переводимой строке:

Копируется %2 файлов, сейчас копируется %1 файл

Copyng file %1 of %2

Этим в частности объясняется, почему механизм переводов сделан таким «неудобным», с вашей точки зрения.

Я делаю так: храню в Translator список строк-аргументов (positional arguments), при вызове метода, возвращающего переведенную строку, они подставляются простым QString::arg. Как-то так:
class Translation
{
//Тут то что уже было в статье
private:
    QStringList arguments;
public:
    void setArguments(const QStringList &list) { arguments = list; }
    QString toString() const
    {
        QString s = QCoreApplication::translate(context.toUtf8().constData(), sourceText.toUtf8().constData(),
                                    disambiguation.toUtf8().constData(), n);
        foreach (const QString &a, arguments)
            s = s.arg(a);
        return s;
    }
};

//Где-то в коде:

for (int i = 0; i < 10; ++i) {
    QWidget *w = new QWidget;
    Translation t = Translation::translate("context", "Widget number #%1");
    t.setArguments(QStringList() << QString::number(i + 1));
    w->setWindowTitle(t);
    new DynamicTranslator(w, "windowTitle", t);
}

В Qt Linguist видим «Widget number #%1», переводим, к примеру, «Виджет номер #%1». Соответственно, будет 10 виджетов с номерами от 1 до 10, скажем, «Виджет номер #7».

Конечно, это не избавит от необходимости осуществить повторный вызов setWindowTitle вручную, если номер сменится, но это уже совсем другая история.
Ну вот, ленивый перевод в действии, о чем я и говорил — сам перевод и сборка строки происходят в тот момент, когда в них появляется необходимость.
Нет. Все формы Qt переводит сама по languageChange событию (если в формах прописаны алиасы и стоит галочка «translatable»). Руками в changeEvent нужно переводить только строки, которые вы сами динамически ставите в контролы или рисуете, т. е. то, что не в .ui-файлах.
Разве? Вроде UIC только генерирует соответствующий код для «своих» виджетов в методе retranslateUi(), а вызывать этот метод надо самостоятельно в переопредлённом обработчике changeEvent().
Согласен, что архитектура у меня не всегда соответствует общепринятым стандартам (тот же «модель-представление», упомянутый в комментарии выше). Однако, я глубоко убежден, что не стоит переусложнять то, что можно оставить простым без ущерба для разработчика и пользователя. Иначе говоря, KISS. Как показывает мой (хоть и небольшой пока что) опыт, добавить новый уровень абстракции, буде в нем возникнет необходимость, обычно можно без особых проблем, если, конечно, изначально руководствоваться здравым смыслом и немножко думать о будущем.
Как-то у меня всё проще (хорошее ли это решение или нет — другой вопрос, просто делюсь своим вариантом. Просьба не минусовать). Linguist не использую, вместо него самописная утилита, которая складывает тексты в файл в формате alias=translation (можно и XML, конечно). Мой собственный наследник QTranslator его загружает и переопределяет виртуальный метод translate. Самое интересное — при смене языка в настройках я делаю

QApplication::postEvent(QCoreApplication::instance(), new QEvent(QEvent::LanguageChange));

Всё, весь текст, прописанный в .ui-формах алиасами переведен. Динамически формируемый текст нужно обновлять в обработчике LanguageChange.
НЛО прилетело и опубликовало эту надпись здесь
Такая задача, скорее всего, тянет на отдельную диссертацию по компьютерной лингвистике (как и с универсальным интернациональным алгоритмом расстановки переносов).
Никогда почему-то не задумывался о необходимости на лету во время выполнения приложения переключать языки. Ну, просто даже не как программист, а как пользователь.
Для меня это все же пока остается «Установил приложение, запустил, выбрал язык в настройках, все»

P.S. QTranslator активно использую, правда NOOP гораздо реже.
Пример юзкейса — интерфейс на терминале в аэропорту. Язык будет переключаться регулярно и желательно делать это очень быстро.

Другой вопрос, стоит ли делать терминал на Qt. Хотя как раз требование моментальной отзывчивости приложения и может послужить в пользу выбора Qt для этой цели.
Действительно, Вы правы.
Как то я тоже столкнулся с такими неудобствами, решением было написать свой простой движок на основе QHash.
Я рассказывал о нем в этой статье.
Получил плюсы: можно сохранять языки в свой формат файла, пользователи программы могут редактировать перевод без установки Qt Linguist.
Благодаря тому, что виджет w является родителем нашего DynamicTranslator, нет необходимости беспокоиться о его удалении — DynamicTranslator будет удалён вместе с QWidget.

Строго говоря, с тем кодом, что приведен в статье, это не так, так как вы не передается parent из своего конструктора выше по иерархии.
Благодарю, поправил.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории