Entry tags:
Локализация cpp-шного проекта
(интересно только программистам)
Вот, встала тут такая задачка. В очередной раз, не знаю, уже, в какой. Дан проект средних размеров - 200 с лишним cpp-шников. В нем - куча строковых литералов (не Unicode) на английском. Требуется сделать по крайней мере двуязычие (rus+eng).
Задачку я уже решил, но так получилось, что "на выходе" образовались кое-какие полезные идеи, которыми хочу поделиться. Вдруг кому-нибудь пригодится.
Upd:
Все плюшки здесь
Пришлось напейсать программу-локализатор, которая автоматизирует большую часть процесса.
"Ручной труд" применялся, в основном, на начальном этапе - когда надо было определиться с тем, какие строки подлежат локализации, а какие - нет. Я просто пометил все такие строки уникальным идентификатором LS. Например, вместо "&Cancel" стало LS"&Cancel". Потом определил в проекте
- чтобы программа с этими пометками нормально компилировалась.
Думаю, и эту часть работы можно было частично автоматизировать - отметить префиксами вообще все строки, содержащие латинские буквы, а потом только убирать ненужные префиксы. Это дает меньший объем работы, чем обратная операция - расстановка префиксов. Наверное, я добавлю в локализатор и такую фишку - ради достижения гештальта :)
Дальше я возился с локализатором, который потом выполнил всю грязную работу:
1. Вытащил список исходников из проекта Visual Studio.
2. Просканировал их на предмет помеченных строк.
3. Собрал все эти строки в один "файл локализации", имеющий такой формат:
Строки, которые оказались одинаковыми, объединяются в одну.
В этом месте были две "приятности", которой не было в прежних вариантах локализации, с которыми я имел дело.
Во-первых, "строка" пишется as is - со всеми переводами строк, табуляциями и даже нулевыми символами. Никаких escape-последовательностей не требуется, что очень удобно. Само собой, в исходниках escape-последовательности были (вроде \r\n), но их локализатор "раскрыл". Признаком конца строки является магическая последовательность "/END/". Да, я в курсе, что магические последовательности - это как бы нехорошо, но тут, похоже, исключение из правила.
Во-вторых, идентификатор_строки не надо мучительно выдумывать, он генерится сам по первым буквам содержимого строки. Получается что-то типа:
Могут получиться одинаковые идентификаторы, если две строки начинаются похоже. Тогда добавляются (автоматически) суффиксы __2, __3, __4... Пример:
4. Потом все строки в исходниках были заменены на идентификаторы по принципу:
LS__Amplitude__6 вместо LS"Amplitude: %g"
Тут еще одна полезная фишка: LS__Amplitude__6 - это есть просто const char *, которой значение присваивается на runtime, при старте программы. В результате программу после локализации почти не пришлось переделывать. Единственный узкий момент - это инициализация статических переменных литералами.
Возникли два основных случая:
- превращается в:
- и это compile error.
Для исправления эта строка просто убирается, и везде вместо mystring пишется LS__mystring.
Другой случай:
- превращается в:
- и это неприятно, поскольку указатель LS__blin инициализируется слишком поздно.
Это исправляется так:
- и, соответственно, везде вместо pups.text используется *(pups.text).
5. Потом были сгенерированы объявления локализованных строк через extern.
И вот тут очередная "вкусность". Во всех прежних проектах подобные идентификаторы были объявлены все сразу в одном хэдере. Этот хэдер #include-ился повсеместно. Малейшее изменение в локализации - и получите перекомпиляцию всех файлов проекта. Неудобно.
Теперь же все выглядит так:
Объявления генерятся и исправляются автоматически, и даже местечко для них подбирается автоматом: пропускаются все комментарии и директивы в начале файла и блок вставляется перед первым объявлением переменной или первой функцией. Потом блок можно переставить, и локализатор впредь будет совать свои объявления в новое место. Хотя мне ничего переставлять не пришлось - все сработало сразу как надо.
Изменения в локализации теперь затрагивают только те файлы, где действительно что-то необходимо изменить.
6. Ну дальше все просто. На основе "файла локализации" сгенерил cpp-шный модуль, где определены (а не объявлены) все эти указатели вроде LS__Search_complete. Файл добавил в проект. Потом сгенерил бинарный файл, который содержит сами строки и добавил его загрузку в функцию main. Сам загрузчик получился крошечный (естественно, это тоже модуль проекта), а его вызов - и вовсе несколько строк кода:
LS_all - это... эээ... массив указателей на указатели. То есть, указатель на указатель на указатель на char :) Но заморачиваться не стоит, поскольку он тоже генерится сам.
7. После этого мне осталось только сделать вариант "файла локализации" на русском языке и сгенерировать другой бинарник greenwin_loc.bin. Заменяя бинарники и перезапуская программу, я меняю язык:


Там еще много мелких плюшек вроде автоматического "merge" двух "файлов локализации" на разных языках, но это уже мелочи. Основное рассказал.
Программой-локализатором, исходником загрузчика и help-ом к ним могу поделиться, но сразу скажу: переделывать это дело под чьи-то нужды, например, под Unicode, мне будет лениво :)
Вот, встала тут такая задачка. В очередной раз, не знаю, уже, в какой. Дан проект средних размеров - 200 с лишним cpp-шников. В нем - куча строковых литералов (не Unicode) на английском. Требуется сделать по крайней мере двуязычие (rus+eng).
Задачку я уже решил, но так получилось, что "на выходе" образовались кое-какие полезные идеи, которыми хочу поделиться. Вдруг кому-нибудь пригодится.
Upd:
Все плюшки здесь
Пришлось напейсать программу-локализатор, которая автоматизирует большую часть процесса.
"Ручной труд" применялся, в основном, на начальном этапе - когда надо было определиться с тем, какие строки подлежат локализации, а какие - нет. Я просто пометил все такие строки уникальным идентификатором LS. Например, вместо "&Cancel" стало LS"&Cancel". Потом определил в проекте
#define LS
- чтобы программа с этими пометками нормально компилировалась.
Думаю, и эту часть работы можно было частично автоматизировать - отметить префиксами вообще все строки, содержащие латинские буквы, а потом только убирать ненужные префиксы. Это дает меньший объем работы, чем обратная операция - расстановка префиксов. Наверное, я добавлю в локализатор и такую фишку - ради достижения гештальта :)
Дальше я возился с локализатором, который потом выполнил всю грязную работу:
1. Вытащил список исходников из проекта Visual Studio.
2. Просканировал их на предмет помеченных строк.
3. Собрал все эти строки в один "файл локализации", имеющий такой формат:
идентификатор_строки строка /END/ идентификатор_строки строка /END/ ...
Строки, которые оказались одинаковыми, объединяются в одну.
В этом месте были две "приятности", которой не было в прежних вариантах локализации, с которыми я имел дело.
Во-первых, "строка" пишется as is - со всеми переводами строк, табуляциями и даже нулевыми символами. Никаких escape-последовательностей не требуется, что очень удобно. Само собой, в исходниках escape-последовательности были (вроде \r\n), но их локализатор "раскрыл". Признаком конца строки является магическая последовательность "/END/". Да, я в курсе, что магические последовательности - это как бы нехорошо, но тут, похоже, исключение из правила.
Во-вторых, идентификатор_строки не надо мучительно выдумывать, он генерится сам по первым буквам содержимого строки. Получается что-то типа:
Any_changes_will_be_reflected Any changes will be reflected after program restart. Do it now? /END/
Могут получиться одинаковые идентификаторы, если две строки начинаются похоже. Тогда добавляются (автоматически) суффиксы __2, __3, __4... Пример:
Amplitude A&mplitude: /END/ Amplitude__2 Amplitude: /END/ Amplitude__3 Amplitude: - /END/ Amplitude__4 &Amplitude: /END/ Amplitude__5 Amplitude /END/ Amplitude__6 Amplitude: %g /END/
4. Потом все строки в исходниках были заменены на идентификаторы по принципу:
LS__Amplitude__6 вместо LS"Amplitude: %g"
Тут еще одна полезная фишка: LS__Amplitude__6 - это есть просто const char *, которой значение присваивается на runtime, при старте программы. В результате программу после локализации почти не пришлось переделывать. Единственный узкий момент - это инициализация статических переменных литералами.
Возникли два основных случая:
static char mystring[]= LS"mystring";
- превращается в:
static char mystring[]= LS__mystring;
- и это compile error.
Для исправления эта строка просто убирается, и везде вместо mystring пишется LS__mystring.
Другой случай:
struct Pupseg { char *text; int id; }; static Pupseg pups = {LS"blin", 12 };
- превращается в:
static Pupseg pups = {LS__blin, 12 };
- и это неприятно, поскольку указатель LS__blin инициализируется слишком поздно.
Это исправляется так:
struct Pupseg { char **text; int id; }; static Pupseg pups = {&LS__blin, 12 };
- и, соответственно, везде вместо pups.text используется *(pups.text).
5. Потом были сгенерированы объявления локализованных строк через extern.
И вот тут очередная "вкусность". Во всех прежних проектах подобные идентификаторы были объявлены все сразу в одном хэдере. Этот хэдер #include-ился повсеместно. Малейшее изменение в локализации - и получите перекомпиляцию всех файлов проекта. Неудобно.
Теперь же все выглядит так:
/* ...blablabla */ #include "gwbase.h" #include "leakon.h" #include "grichedit.h" ...blablabla /*--LOCALIZER DECLARATIONS-- Localizer: LS --BEGIN-- */ extern const char *LS__Search_complete, *LS__Replace_complete, *LS__d_replacement_s_are_made; /*--LOCALIZER DECLARATIONS-- Localizer: LS --END-- */
Объявления генерятся и исправляются автоматически, и даже местечко для них подбирается автоматом: пропускаются все комментарии и директивы в начале файла и блок вставляется перед первым объявлением переменной или первой функцией. Потом блок можно переставить, и локализатор впредь будет совать свои объявления в новое место. Хотя мне ничего переставлять не пришлось - все сработало сразу как надо.
Изменения в локализации теперь затрагивают только те файлы, где действительно что-то необходимо изменить.
6. Ну дальше все просто. На основе "файла локализации" сгенерил cpp-шный модуль, где определены (а не объявлены) все эти указатели вроде LS__Search_complete. Файл добавил в проект. Потом сгенерил бинарный файл, который содержит сами строки и добавил его загрузку в функцию main. Сам загрузчик получился крошечный (естественно, это тоже модуль проекта), а его вызов - и вовсе несколько строк кода:
extern const char **LS_all[]; return sys.localizer.load("greenwin_loc.bin", LS_all);
LS_all - это... эээ... массив указателей на указатели. То есть, указатель на указатель на указатель на char :) Но заморачиваться не стоит, поскольку он тоже генерится сам.
7. После этого мне осталось только сделать вариант "файла локализации" на русском языке и сгенерировать другой бинарник greenwin_loc.bin. Заменяя бинарники и перезапуская программу, я меняю язык:


Там еще много мелких плюшек вроде автоматического "merge" двух "файлов локализации" на разных языках, но это уже мелочи. Основное рассказал.
Программой-локализатором, исходником загрузчика и help-ом к ним могу поделиться, но сразу скажу: переделывать это дело под чьи-то нужды, например, под Unicode, мне будет лениво :)
Re: От лица мазохиста :)
>систем.
это тебя кто-то сильно обманул: как была аутсайдером, так и осталась. на рынке «падучее нечто для домохозяйки» — тут да, тут ей конкурентов нет. потому что сложно так испортить хорошую систему, чтобы она в этот рынок влезла (хотя у каноникал получается, увы).
>То что рынок сильно инерционен (счет идет на многие годы), еще не
>означает что на нём в конечном счете не выигрывает лучшее.
угу. потому что инерционность — только следствие. «невидимая рука рынка» существует в грибных иллюзиях, в раеле её нет. на рынке востребовано не лучшее, а распиареное.
>Продавайте ему технику с предустановленной виндой — будет винда удобней.
>Не аргумент.
ещё как аргумент. собственно, так и происходит. если бы никому в три жопы ненужную свисту не предустанавливали — была бы она сейчас только в зоопарках, где за решётками сидят фанаты m$.
>Полностью автоматизировать последующий процесс обновления софта
>невозможно. Будьте реалистом.
однако же у линуксоидов получается. я вот уже который год запускаю slapt-get с просьбой проапдейтить мой софт, и если за всё время мне два раза пришлось что-то руками допилять, так это потому, что изначально я софт не из слаковских реп ставил, а сам зачем-то напильником точил.
>Я юзаю C++ по доброй воле :D
ну… бывает, что тут скажешь. я вот видел человека, который добровольно под однокристалки на асме писал. при том, что вся остальная контора делала код на C. ну, и в итоге код ярого ассемблерщика ни в сроки разработки не укладывался, ни безглючностью не отличался.
>Вот и решайте тогда проблемы с линковкой про себя, а не жалуйтесь
а где я на линковку ругался? я ругаюсь на архаичные костыли, которые пронизали всю IT. описываемые проблемы — просто частный случай запущеной болезни.
>Если вам проще извращаться со смешением кода — не вопрос! Но это ваш
>выбор, и жаловаться на «плохие языки программирования» в таком случае —
>то же самое, что танцору говорить, что ему ноги танцевать мешают :D
нет, мне проще было бы писать на нормальные лисп-процессоры. но это нифига не реально, потому что даже если их и будут массово производить, быдлокодерская масса потребует на них своих любимых жаб, сей, дельфей и прочей дребедени.
>Такие хорошие команды — и вдруг предпочитают C++
угу. потому что они кушать хотят. другие хорошие команды давно имеют свои in-house инструменты. а остальные пишут на цпп, ибо иначе череп взорвётся. и потому им можно продать только цпп. вот и делают на цпп, чтобы не побираться по помойкам.
>Кстати, «хорошую команду», сделавшую что-либо великое на Lua и Lisp-е —
>назовете?
Lua не является самостоятельным языком.
для лиспа могу назвать конторы Symbolics и LMI, если тебе это что-то говорит. а также emacs. ничего более сильного не будет, потому что смотри выше про помойки и продажи.
>Но это не нормальная ситуация, соответствующая «полудохлой», а то и
>"умирающей" команде. Таких, к сожалению, из-за «доткомовского пузыря» и
>было, и остается немало — но они прожирают деньги инвесторов и
>проигрывают более эффективным командам.
возможно, где-то в волшебной стране Утопии всё делают по-уму. к сожалению, туда меня жить не берут. а на Земле интеллектуальные задачи при помощи интеллекта решают в 0.0001% случаев. это если я по оптимистичности не переоценил, конечно.
Re: От лица мазохиста :)
Позиции сторон озвучены. Мне кажется, что к сказанному добавить больше нечего.
Re: От лица мазохиста :)
гордо задирая нос: да, я такой.
>Позиции сторон озвучены. Мне кажется, что к сказанному добавить больше
>нечего.
— с антропоцентристами дискутировать не желаю.
— тогда давай анекдоты рассказывать.
(ц)
%-)
Re: От лица мазохиста :)