В общем, под это дело можно и отдельный топик. Итак: новая версия компонента. Полного автоматизма нет, и, как я решил, не будет. Но не пугайтесь, все стало еще проще В архиве содержится пакадж с компонентом, который надо установить перед открытием тестового проекта (если вы захотите поиграть с проектом).
Собственно, что нового? Давайте попробую рассказать по шагам, как пользоваться этим добром теперь:
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. Как обычно вы создаете компоненты? Я — примерно так:
То есть имени у этого компонента нет, ссылаться при экспорте непонятно на что и вроде бы косяк. Нет, не косяк. Специально для этого случая в компоненте предусмотрена возможность задания имени таким компонентам, причем имя подбирается так, чтобы не конфликтовать с другими именами компонент в контейнере.
Теперь о птичках: как-же локализовать такой компонент? Очень просто, добавлением всего одной строчки:
И все. Но лучше, конечно, во избежание коллизий все-таки давать имена компонентам. Я не тестировал и пока не знаю, как поведет себя компонент при условии, что есть две формы в приложении и в каждой из них создается вот по такой безымянной кнопке. Подозреваю, что одна из них перестанет переводится, хотя не уверен и может быть все будет работать.
2. Как быть, если у вас в проекте много форм, фреймов и пр., как автосоздающихся, так и создающихся в коде? Здесь уже ручками. Поскольку я принял решение не использовать Screen->OnActiveFormChange, а Loaded не всегда полезен (особенно, когда компонент лежит на TDataModule — там вообще транслировать нечего ), то вы можете воспользоваться одним из следующих методов:
а. Самостоятельно заюзать Screen->OnActiveFormChange и дергать Translate для активной формы.
б. После создания, но перед показом формы/фрейма/пр. дернуть Translate для этой формы
в. В OnCreate нужной формы дергуть Translate
Не рекомендую тыркать компонент во все формы, ибо это излишне. Лучше всего создать дата-модуль и туда его, туда И дергать Translate при необходимости.
З.Ы. Да, если что — не серчайте, что мог обмануть ваши ожидания в каком-то из пунктов. Во-первых, не все сразу. Во-вторых, я делаю так, как мне удобнее, что очевидно.
Re[2]: Компонент для перевода интерфейса - версия 1.0а
Здравствуйте, alive, Вы писали:
A>Если уважаемый Flamer не возражает то версия для Delphi здесь
Уважаемый Flamer не возражает, уважаемый Flamer очень даже за Чертовски приятно, все-таки, назвать себя пару раз уважаемым И главное — отмазка есть: если спросят — "ты чего мол?", скажу, что процитировал два раза уважаемого alive
З.Ы. А если серьезно — спасибо за вариант для Delphi. Думаю, теперь будет не так много добавлений/исправлений, так что если вы при случае и желании найдете возможность перевести дальнейшие добавления — я буду только рад.
... << RSDN@Home 1.1.3 stable >>
Re[3]: Компонент для перевода интерфейса - версия 1.0а
Выложена новая версия. Процитирую добавления (history находится в InterfaceHelper.h, если вдруг кому интересно):
Поскольку вызов события сама по себе дорогостоящая операция, добавлено свойство IgnoreComponentTag. То есть тому компоненту, чьи свойства вы не хотите импортить/экспортить, устанавливаете нужное значение (например, -100) в свойство Tag, это же значение указываете в свойстве IgnoreComponentTag компонента и все.
То есть, если вы не хотите, чтобы какой-либо компонент переводился — указываете ему Tag в любое число и это-же число указываете в значении свойства IgnoreComponentTag компонента TInterfaceTranslator. Все.
Здравствуйте, Flamer, Вы писали:
F>Уважаемый Flamer не возражает, уважаемый Flamer очень даже за Чертовски приятно, все-таки, назвать себя пару раз уважаемым И главное — отмазка есть: если спросят — "ты чего мол?", скажу, что процитировал два раза уважаемого alive
Прямо как у Райкина "Ты меня уважаешь. Я тебя уважаю. Мы с тобой уважаемые люди"
А если серьёзно — все работает здорово И коллекции можно будет добавить а если поизвращаться то наверное и TListItems и TTreeNodes. Но вот что делать со строковыми ресурсами? Вот у меня в проекте приличное количество resourcestrings — сообщения, строки форматирования.
[]
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а
Здравствуйте, 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а
Здравствуйте, 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а
* 26.11.2004 — Дописана возможность выгрузки/загрузки custom-строк.
Для этого введена проперть CustomSectionName, и метод Translate,
который транслирует строку. Как он работает: в режиме экспорта
для переданной строки создается хэш, и строка с этим хэшем помещается
в хранитель интерфейса как, соответственно, PropertyName и PropertyValue.
Возвращается переданная строка. В режиме трансляции в хранителе
интерфейса выбирается секция CustomSectionName, для переданной строки
строится хэш и по этому хэшу ищется переведенная строка. Если она
найдена — возвращается перевод. Если же нет — возвращается переданная
строка.
То есть о чем я: допустим, вы захардкодили вот такой код:
То есть что получаем: если сразу хардкодить всякие сообщения таким способом, то получаем несомненный выигрыш: если вдруг файла интерфейса не окажется — сообщения останутся на захардкоденном языке.
З.Ы. Как будет время, допишу функцию Translate, которая будет работать с ресурсами, т.е. транслировать строки по переданному ResourceID. Соответственно, сфера применения немного расширится.
З.З.Ы. Теперь в моем последнем проекте наконец-то можно прикручивать переводилку интерфейса — все, что пока мне нужно, уже есть
Re[2]: Компонент для перевода интерфейса - версия 1.0а
Здравствуйте, Flamer, Вы писали:
F>>>Обновил компонент до версии 1.1a. Лежит здесь: http://www.rsdn.ru/File/4597/InterfaceTranslator1.1.a.zip
R>>А ссылочка-то жестоко нерабочая.
F>Да ну? Только что скачал и все прекрасно...
Хм, извиняюсь за тревогу, тоже скачал.
Сервер никак не давал — дисконнектил. С 20го раза скачалось.