Компонент для перевода интерфейса - версия 1.0а
От: Flamer Кипр http://users.livejournal.com/_flamer_/
Дата: 01.10.04 15:45
Оценка: 28 (3)
#Имя: SRC.interface.multilang
В общем, под это дело можно и отдельный топик. Итак: новая версия компонента. Полного автоматизма нет, и, как я решил, не будет. Но не пугайтесь, все стало еще проще В архиве содержится пакадж с компонентом, который надо установить перед открытием тестового проекта (если вы захотите поиграть с проектом).

Собственно, что нового? Давайте попробую рассказать по шагам, как пользоваться этим добром теперь:

1. Бросаете на форму компонент TInterfaceTranslator (искать надо на вкладке "Flamer" в палитре ).
2. Указываете имя файла.
3. Устанавливаете Active в true.
4. Компилируете и запускаете приложение.
5. Получаете выходной файл с оригинальным интерфейсом.

То есть компонент изначально настроен на экспорт. Это легко изменить, установив в Object Inspector свойство TranslationMode = tmTranslate. Теперь компонент будет устанавливать интерфейс из файла, указанного в свойстве FileName.
Да, еще: к сожалению, никакой design-time выгрузки нет и вряд-ли будет, т.к. раз уж это дело в проекте юзается, то надо юзать по-жизни, а не только в design-time


Как переключать интерфейс? До обидного просто (примем за постулат, что Active у компонента выставлено в true и TranslationMode равно tmTranslate):

InterfaceTranslator->FileName = "russian.txt"; // автоматом русский язык стал во всех доступных формах
InterfaceTranslator->FileName = "english.txt"; // вернули басурманский взад :)

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

1. Как обычно вы создаете компоненты? Я — примерно так:

TButton* btn = new TButton(this);
btn->Parent = this;
btn->Caption = "Dynamic Button";

То есть имени у этого компонента нет, ссылаться при экспорте непонятно на что и вроде бы косяк. Нет, не косяк. Специально для этого случая в компоненте предусмотрена возможность задания имени таким компонентам, причем имя подбирается так, чтобы не конфликтовать с другими именами компонент в контейнере.

Теперь о птичках: как-же локализовать такой компонент? Очень просто, добавлением всего одной строчки:

TButton* btn = new TButton(this);
btn->Parent = this;
btn->Caption = "Dynamic Button";

InterfaceTranslator->Translate(btn);

И все. Но лучше, конечно, во избежание коллизий все-таки давать имена компонентам. Я не тестировал и пока не знаю, как поведет себя компонент при условии, что есть две формы в приложении и в каждой из них создается вот по такой безымянной кнопке. Подозреваю, что одна из них перестанет переводится, хотя не уверен и может быть все будет работать.


2. Как быть, если у вас в проекте много форм, фреймов и пр., как автосоздающихся, так и создающихся в коде? Здесь уже ручками. Поскольку я принял решение не использовать Screen->OnActiveFormChange, а Loaded не всегда полезен (особенно, когда компонент лежит на TDataModule — там вообще транслировать нечего ), то вы можете воспользоваться одним из следующих методов:

а. Самостоятельно заюзать Screen->OnActiveFormChange и дергать Translate для активной формы.
б. После создания, но перед показом формы/фрейма/пр. дернуть Translate для этой формы
в. В OnCreate нужной формы дергуть Translate

Не рекомендую тыркать компонент во все формы, ибо это излишне. Лучше всего создать дата-модуль и туда его, туда И дергать Translate при необходимости.

В общем, сами увидите в тестовом проекте, насколько все на самом деле стало просто. Сам архив (302 Кб) вместе с тестовым exe доступен здесь: http://www.rsdn.ru/File/4597/InterfaceTranslator.1.0.zip

Как обычно — поздравления и аплодисменты

З.Ы. Да, если что — не серчайте, что мог обмануть ваши ожидания в каком-то из пунктов. Во-первых, не все сразу. Во-вторых, я делаю так, как мне удобнее, что очевидно.
Re[2]: Компонент для перевода интерфейса - версия 1.0а
От: alive Россия  
Дата: 04.10.04 20:26
Оценка: 10 (1)
F>[]

Если уважаемый Flamer не возражает то версия для Delphi здесь
Keep yourself alive
Re[3]: Компонент для перевода интерфейса - версия 1.0а
От: Flamer Кипр http://users.livejournal.com/_flamer_/
Дата: 04.10.04 20:45
Оценка: :)
Здравствуйте, alive, Вы писали:

A>Если уважаемый Flamer не возражает то версия для Delphi здесь


Уважаемый Flamer не возражает, уважаемый Flamer очень даже за Чертовски приятно, все-таки, назвать себя пару раз уважаемым И главное — отмазка есть: если спросят — "ты чего мол?", скажу, что процитировал два раза уважаемого alive

З.Ы. А если серьезно — спасибо за вариант для Delphi. Думаю, теперь будет не так много добавлений/исправлений, так что если вы при случае и желании найдете возможность перевести дальнейшие добавления — я буду только рад.
... << RSDN@Home 1.1.3 stable >>
Re[3]: Компонент для перевода интерфейса - версия 1.0а
От: Flamer Кипр http://users.livejournal.com/_flamer_/
Дата: 01.12.04 17:43
Оценка: +1
Здравствуйте, rastoman, Вы писали:

R>Здравствуйте, Flamer, Вы писали:


F>>Обновил компонент до версии 1.1a. Лежит здесь: http://www.rsdn.ru/File/4597/InterfaceTranslator1.1.a.zip


R>А ссылочка-то жестоко нерабочая.


Да ну? Только что скачал и все прекрасно...
Re: Компонент для перевода интерфейса - версия 1.0а
От: Flamer Кипр http://users.livejournal.com/_flamer_/
Дата: 02.10.04 16:57
Оценка:
Здравствуйте, Flamer, Вы писали:

[]

Выложена новая версия. Процитирую добавления (history находится в InterfaceHelper.h, если вдруг кому интересно):

Поскольку вызов события сама по себе дорогостоящая операция, добавлено свойство IgnoreComponentTag. То есть тому компоненту, чьи свойства вы не хотите импортить/экспортить, устанавливаете нужное значение (например, -100) в свойство Tag, это же значение указываете в свойстве IgnoreComponentTag компонента и все.


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

Обновленный package, теперь уже без тестового проекта, один только package (20 Kb), лежит по адресу http://www.rsdn.ru/File/4597/InterfaceTranslatorPackage.1.0a.zip

Как обычно — замечания, предложения велкамствуются.

З.Ы. Экспортом/импортом коллекций займусь, как придет вдохновение, то есть появится достаточно свободного времени
Re[4]: Компонент для перевода интерфейса - версия 1.0а
От: alive Россия  
Дата: 05.10.04 08:33
Оценка:
Здравствуйте, Flamer, Вы писали:

F>Уважаемый Flamer не возражает, уважаемый Flamer очень даже за Чертовски приятно, все-таки, назвать себя пару раз уважаемым И главное — отмазка есть: если спросят — "ты чего мол?", скажу, что процитировал два раза уважаемого alive


Прямо как у Райкина "Ты меня уважаешь. Я тебя уважаю. Мы с тобой уважаемые люди"

А если серьёзно — все работает здорово И коллекции можно будет добавить а если поизвращаться то наверное и TListItems и TTreeNodes. Но вот что делать со строковыми ресурсами? Вот у меня в проекте приличное количество resourcestrings — сообщения, строки форматирования.
... << RSDN@Home 1.1.4 beta 3 rev 190 >> <<Круиз — Много нас>>
Keep yourself alive
Re[5]: Компонент для перевода интерфейса - версия 1.0а
От: Flamer Кипр http://users.livejournal.com/_flamer_/
Дата: 05.10.04 09:17
Оценка:
Здравствуйте, alive, Вы писали:

[]

A>А если серьёзно — все работает здорово И коллекции можно будет добавить а если поизвращаться то наверное и TListItems и TTreeNodes.


Подумаю над этим на досуге. Сейчас времени немножко не хватает...

A>Но вот что делать со строковыми ресурсами? Вот у меня в проекте приличное количество resourcestrings — сообщения, строки форматирования.


А вот это уже интересней. Я тут виду какой выход: возможность задавать компоненту название специальной секции, в которой он будет хранить user-defined строки. Как эти строки будут формироваться — пока не знаю. Подозреваю, что лучшим вариантом будет написать утиль, который выцепляет это из ресурсного файла и помещает в выходной.

Использование локализации в этом случае тоже не сильно покорежит код:

Было:
Label1->Caption = LoadFromResource(ResourceID);


Стало:
// будет что-то типа перечисления в настройках компонента:
typedef enum
{
  daLoadFromResource // загрузить из ресурса, если не найдено в файле интерфейса

} TDefaultAction;

Label1->Caption = InterfaceTranslator->Translate(ResourceID,daLoadFromResource);


Как такой подход?
Re[6]: Компонент для перевода интерфейса - версия 1.0а
От: alive Россия  
Дата: 06.10.04 19:08
Оценка:
Здравствуйте, Flamer, Вы писали:

F>А вот это уже интересней. Я тут виду какой выход: возможность задавать компоненту название специальной секции, в которой он будет хранить user-defined строки. Как эти строки будут формироваться — пока не знаю. Подозреваю, что лучшим вариантом будет написать утиль, который выцепляет это из ресурсного файла и помещает в выходной.


Не хотелось бы, конечно, внешних утилит.
Потом, если собирать все строковые ресурсы то это потащит и vcl-ные и ресурсы сторонних компонент — мало не покажется . Но при использовании resourcestring компилятор сам назначает строкам id и отделить "свои" от "чужих" по-моему нельзя.
Как вариант можно подключать строковые ресурсы через rc — файлы в определенном диапазоне id и уже в компоненте указывать этот диапазон для их выцепления, помещения в отдельную секцию выходного файла и перевода.

F>[]


F>Как такой подход?


... << RSDN@Home 1.1.4 beta 3 rev 190 >> <<LED ZEPPELIN — The Song Remains The Same>>
Keep yourself alive
Re[7]: Компонент для перевода интерфейса - версия 1.0а
От: Flamer Кипр http://users.livejournal.com/_flamer_/
Дата: 06.10.04 19:36
Оценка:
Здравствуйте, alive, Вы писали:

A>Потом, если собирать все строковые ресурсы то это потащит и vcl-ные и ресурсы сторонних компонент — мало не покажется . Но при использовании resourcestring компилятор сам назначает строкам id и отделить "свои" от "чужих" по-моему нельзя.

A>Как вариант можно подключать строковые ресурсы через rc — файлы в определенном диапазоне id и уже в компоненте указывать этот диапазон для их выцепления, помещения в отдельную секцию выходного файла и перевода.

Да, пожалуй, в любом случае автоматизмом и не запахнет на 100%. Ну что-же, раз мы с вами тут беседуем вдвоем, тогда можно сказать, что мы пришли к выводу, как наиболее прозрачно сделать custom-секцию в файле с интерфейсом: сама секция делается ручками с нужным именем (раз пока один ini-like формат выгрузки — это вдвойне просто), а для случаев, когда имеются ресурсные строки, просто зашитые в код строки и пр. я предусмотрю следующие методы:

// будет что-то типа перечисления в настройках компонента:
typedef enum
{
  daLoadFromResource, // загрузить из ресурса, если не найдено в файле интерфейса
    daReturnDefault // вернуть значение, переданное в функцию

} TDefaultAction;

String __fastcall Translate(int ID, TDefaultAction DefaultAction, const String& DefaultValue="");
String __fastcall Translate(String ID, TDefaultAction DefaultAction, const String& DefaultValue="");
// ну и еще пяток перегруженных, если будет необходимость ;)


То есть, в первом приближении, работа будет вестись так:
// задали имя секции, в которой определенные нами настройки
InterfaceTranslator->CustomSectionName = "MYSECTION";

// пытаемся загрузить из ресурса с ID 100, если строка вида "100=bla-bla" не найдена в файле интерфейса
Label1->Caption = InterfaceTranslator->Translate(100,daLoadFromResource);

// возвращаем дефолтное значение, если строка вида "100=bla-bla" не найдена в файле интерфейса
Label1->Caption = InterfaceTranslator->Translate(100,daReturnDefault,"Default value");


В таком вот аксепте, в общем. То есть вроде как почти убиваем двух зайцев, за исключением того, что секцию [MYSECTION] (как в нашем примере) придется делать ручками.

В общем, если это у вас не вызывает возражений — имплементирую к следующему релизу.

З.Ы. Пока писал, понял, что лучше сделать не enum, а set для обеспечения перебора методов поиска:
typedef enum
{
  daLoadFromResource, // загрузить из ресурса, если не найдено в файле интерфейса
    daReturnDefault // вернуть значение, переданное в функцию

} TDefaultAction;

typedef System::Set<TDefaultAction, daLoadFromResource,daReturnDefault> TDefaultActions;

// ну и потом делаем:
TDefaultActions actions = TDefaultActions() << daLoadFromResource << daReturnDefault;
// то есть компонент будет искать в ресурсах, если не найдет, то вернет дефолтное значение.
// а если добавится еще один путь для поиска - совсем хорошо :)
... << RSDN@Home 1.1.3 stable >>
Re[8]: Компонент для перевода интерфейса - версия 1.0а
От: alive Россия  
Дата: 07.10.04 18:09
Оценка:
Здравствуйте, Flamer, Вы писали:


F>[]

F>В общем, если это у вас не вызывает возражений — имплементирую к следующему релизу.

Хорошо. Навскидку тяжело сказать как будет лучше. Нужно смотреть код.

Кстати по поводу костыля в конструкторе и перехода не метаклассы. То, что я предлагал здесь
Автор: alive
Дата: 01.10.04
подойдет?
... << RSDN@Home 1.1.4 beta 3 rev 192 >> <<Павел Кашин — Я найду тебя>>
Keep yourself alive
Re: Компонент для перевода интерфейса - версия 1.0а
От: Flamer Кипр http://users.livejournal.com/_flamer_/
Дата: 26.11.04 17:54
Оценка:
Здравствуйте, Flamer, Вы писали:

[]

Обновил компонент до версии 1.1a. Лежит здесь: http://www.rsdn.ru/File/4597/InterfaceTranslator1.1.a.zip

Что, собственно, добавлено:

* 26.11.2004 — Дописана возможность выгрузки/загрузки custom-строк.
Для этого введена проперть CustomSectionName, и метод Translate,
который транслирует строку. Как он работает: в режиме экспорта
для переданной строки создается хэш, и строка с этим хэшем помещается
в хранитель интерфейса как, соответственно, PropertyName и PropertyValue.
Возвращается переданная строка. В режиме трансляции в хранителе
интерфейса выбирается секция CustomSectionName, для переданной строки
строится хэш и по этому хэшу ищется переведенная строка. Если она
найдена — возвращается перевод. Если же нет — возвращается переданная
строка.


То есть о чем я: допустим, вы захардкодили вот такой код:

ShowMessage("Hello, World!");


Чего делать? Захардкодивать так:

ShowMessage(InterfaceTranslator->Translate("Hello, World!"));


Можно дефайн какой сделать, чтоб не мучиться:

#define _X(str) InterfaceTranslator->Translate( (str) )

ShowMessage( _X("Hello, World!") );


То есть что получаем: если сразу хардкодить всякие сообщения таким способом, то получаем несомненный выигрыш: если вдруг файла интерфейса не окажется — сообщения останутся на захардкоденном языке.

З.Ы. Как будет время, допишу функцию Translate, которая будет работать с ресурсами, т.е. транслировать строки по переданному ResourceID. Соответственно, сфера применения немного расширится.

З.З.Ы. Теперь в моем последнем проекте наконец-то можно прикручивать переводилку интерфейса — все, что пока мне нужно, уже есть
Re[2]: Компонент для перевода интерфейса - версия 1.0а
От: rastoman  
Дата: 01.12.04 15:10
Оценка:
Здравствуйте, Flamer, Вы писали:

F>Обновил компонент до версии 1.1a. Лежит здесь: http://www.rsdn.ru/File/4597/InterfaceTranslator1.1.a.zip


А ссылочка-то жестоко нерабочая.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
Re[4]: Компонент для перевода интерфейса - версия 1.0а
От: rastoman  
Дата: 02.12.04 08:06
Оценка:
Здравствуйте, Flamer, Вы писали:

F>>>Обновил компонент до версии 1.1a. Лежит здесь: http://www.rsdn.ru/File/4597/InterfaceTranslator1.1.a.zip


R>>А ссылочка-то жестоко нерабочая.


F>Да ну? Только что скачал и все прекрасно...

Хм, извиняюсь за тревогу, тоже скачал.
Сервер никак не давал — дисконнектил. С 20го раза скачалось.
... << RSDN@Home 1.1.4 beta 3 rev. 185>>
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.