Аннотация:
Многие разработчики мечтают о всемирной популярности своих приложений, но почти никто не создает локализованных версий своих приложений, ошибочно полагая, что программа должна сначала завоевать популярность.
Замечание относится непосредственно к MFC. Итак, локализация в MFC делается еще проще: достаточно в редакторе ресурсов приложения выбрать ресурс, подлежащий локализации, кликнуть правой кнопкой мыши и выбрать пункт "Insert Copy". После этого Вас попросят указать язык, на котором будет эта копия. Далее остается только перевести скопированный ресурс — остальное MFC сделает сама.
При загрузке приложения MFC будет искать и загружать ресурсы в зависимости от текущей локали пользователя в следующей последовательности: сначала ищет ресурс с суб_языком = равном локали Windows. Если такового не находится, то она (MFC) пытается загрузить ресурс с языком = локали Windows... Затем английский, затем нейтральный... И если уж не нашлось никакого, то MFC берет ресурс с первым "попавшимся под руку" языком.
Смысл в том, что у программиста не болит голова о локализации... Знай себе, вовремя делай копии ресурсов...
Единственным недостатком предложенного мной метода является то, что MFC грузит ресурсы язык которых соответствует языку Windows, а не региональным настройкам панели управления. То есть, если Вы будете работать на английском Windows, то всегда будете наблюдать английский интерфейс, если локализация выполнена по описанному мной способу.
С другой стороны, это правильное решение: ведь если Вы работаете в ОС на каком-то языке, то это значит, что Вы можете, как минимум, читать на этом языке....
Я не буду комментировать статью, но считаю, что можно сказать так: многие знания — многие печали.
Но мы делаем так (отметьте, что ни в одном из случаев не требуется изменения основной программы при добавлении языка):
Вариант 1.
Все проекты имеют общую ресурсную библиотеку (копать в сторону m_hInstResDLL). После чего все ресурсы переводятся. Проблема как всегда одна: ресурсы рассинхронизируются. Для нахождения несоотвествия между ресурсами я написал по-быстрому недопарсер ресурсов. За 10-15 секунд перелопачивает рассинхронизировавшиеся ресурсы и выдает список несовпадающик контролов в каждом диалоге, ну кое чего еще. Никому не дам :)
Вариант 2. Все используемы строки пишутся так: Xlate(_T("Lya-Lya")). В OnInitDialog() вызывается Xlate(this), который пробегает по всем окошкам и переводит их названия. В функцию Xlate встроена система собра статистики: поработав в этом режим с программой и пробежав по всем диалогам, вы получаете словарь, который остается только перевести. Это работает с приемлемой скоростью даже на 200 мгц стронгарме. Плюсы: никаких проблем с рассинхронизацией и поддержкой! Недостаток: качество перевода, так как иногда перевод зависит от контекста.
Самому переводить свою программу на разные языки? При добавлении нового языка выпускать новую версию? Ну не знаю... Если уж задаваться целью дать каждому возможность увидеть на экране слова на родном языке, путь IMHO изначально тупиковый. Не лучше ли дать пользователям возможность переводить интерфейс как им надо — текстовый файл с переводом всех строк, каталог с родными иконками, как это напрмер сделано в Miranda ICQ — http://miranda-icq.sourceforge.net/ ?
А как Вы тогда объясните то, что все крупные продукты имеют именно локализованные версии, а не текстовые файлы с переводами?
На самом деле здесь дело в возможностях автора. Если этот самы втор один (вдвоём, втроём) работает над программой, то маловероятно, что у него есть возможность локализовать программу самому. В таком случае выбор один — не очень качественный перевод на общественных началах.
Если же автора (чаще компании-автора) есть возможность заказать перевод профессионалам, то локализованные версии — лучший выбор.
В общем, здесь как везде — либо дорого, но качественно, либо дёшево но некачественно.
Борьба с "неполными" ресурсами при локализациях...
После прочтения статьи имею один вопрос, который мне не дает
спокойно жить уже достаточно продолжительное время.
В описаном Вами случае поднимается проблема локализации и загрузки
различных ресурсов. В примерах кода пррисутствует код, который
загружает ресурс по-умолчанию (английский) если не нашлось локальной
копии на загружаемом языке.
Но как в этом случае отследить "неполные" ресурсы? Поясню, что я
имею в виду под термином "неполный". Это, в основном, оносится к
диалоговым ресурсам и ресурсам меню. К примеру, у Вас есть диалог
настроек, который содержить три CheckBox'а (Opt1, Opt2 и Opt3). Также
существует локальная копия этого ресурса, скажем, на русском языке
(Настр1, Настр2 и Настр3).
В один прекрасный момент Вы добавляете в английскую версию еще один
CheckBox и пишете код для работы с ним. Однако по некоторым причинам
Вы не добавляете этого самого CheckBox'а в русский диалог. При
обращении к отсутчтвующему элементу, программа даст сбой.
Как же этого избежать? Как проверить, что ресурс "неполный" и
загрузить всесто его локальной копии копию по умолчанию?
Если Вы, или кто-то из Ваших знакомых решал подобную проблему или
знаете метод решения, пожалуйста, не поленитесь, напишите ответ.
Локализация ресурсов в целом более удобна, хотя и более муторна, чем локализация отдельных строк по нескольким причинам. Во-первых, так рекомендует делать Microsoft, а во вторых небольшой пример.
Например, вы имеете кнопку с надписью "Keyword" — 7 букв. Перевод на русский язык будет выглядеть как "Ключевое слово" — 14 букв. Ровно в два раза больше. И как Вы думаете, влезет ли это словосочетание на кнопку, на которой до этого располагался английский вариант? Я думаю, что нет.
Подобных примеров можно привести массу и не только с русским языком. Практически на любом языке фпаза длиньше, чем аналогичная в английском варианте. Это справедливо и к языкам, отдельные слова на которых короче своих английских "собратьев". Зато там навалом предлогов и артиклей...
> Например, вы имеете кнопку с надписью "Keyword" — 7 букв. > Перевод на русский язык будет выглядеть как > "Ключевое слово" — 14 букв. Ровно в два раза больше. > И как Вы думаете, влезет ли это словосочетание на кнопку, > на которой до этого располагался английский вариант? > Я думаю, что нет.
А я буду делать свою прогу на русском языке, тогда перевод на аглицкий точно влезет куда надо :)
Тем не менее признаю, что мое замечание "Можно но не нужно" не относится к монстрам, разрабатываемым на фирмах уровня Microsoft.
Только при разработке и не хватает этой всякой гадости.
Как ты будешь отлаживать диаложки, если ресурсы недоступны ? Нужно держать на каждый билд его копию — зачем это ? И столько писанины из-за чего ? На два языка это можно в маленькой аппликации. А если исходного кода больше 60МБ и ресурсов — не меньше тысячи формочек? И локализовать нужно на все возможные языки ? Тогда как ?
Материал из Википедии — свободной энциклопедии, -_*
Как это — не доступны? Ресурсы на всех языках, которые поддерживает программа, создаются одновременно.
Копия на каждый билд? Нет! Я предлагаю, ресурсы на всех языках класть в один бил. Тогда ресурс на нужном языке будет загружен автоматически.
А что касается больших приложений, то там для каждого языка действительно создают отдельную конфигурацию (в терминах VC++). Для поддержки такого метода разработки в VC++ есть поле Condition в свойствах ресурса, а это не что иное, как #if #else #endif в файлах ресурсов. Так что здесь тоже нет никаких проблем.
Тут следует отметить вот что. Описанный метод — это модификация метода локализации посредством текстового файла со строками на разных языках. В отличие от последнего он позволяет иметь не только строки на разных языках для элемента управления, но и разное расположение этих самых элементов управления для разных языков! Большое преимущество здесь состоит в том, что, во-первых, как уже отмечалось, одни и те же слова в разных языках имеют разную длину. А во-вторых, носители разных языков, из-за менталитета, воспринимают одни и те же дизайнерские решения по-разному. Например, носители языков, где пишут с права на лево, просматривают окна в порядке не лево верх -> право низ, а право верх -> лево низ. А это ведёт к иному расположению элементов управления в окне.
А что касается больших продуктов, то там выгоднее для каждого языка собирать отдельный билд.
Интересный вопрос. Над ним надо как следует подумать.
А пока могу предложить сравнивать длину загружаемого ресурса и/или его хэш с копией по умолчанию.
А выявлять такие ошибки на стадии компиляции ресурсов было бы вообще чудесно!
>В отличие от последнего он позволяет иметь не только >строки на разных языках для элемента управления, но и >разное расположение этих самых элементов управления >для разных языков!
Да, именно это я и хотел сказать в своем предыдущем постинге, но забыл упомянуть про контролы (вспомнил только при перечитке).
Возвращаясь к приведенному мной примеру, замечу, что для того, чтобы русский вариант написания нормально влез в контрол, достаточно просто растянуть элемент управления и, если необходимо, увеличить размеры диалогового ресурса.
Создание же, изначально, программы на русском языке, а затем ее перевод на другие языки не правилен, с моей точки зрания, по двум причинам: во-первых, если Вы озадачились локаизацией приложения на другие языки, то это подразумевает, что программу будут использовать люди не умеющие читать по русски. А Вы даете гарантию, что приложение установится нормально или никто не сотрет файл с английской локализацией и не удалит Вашу программу только потому, что не сможет понять интерфейс?
Во-вторых, кто Вам сказал, что фразы на русском язык самые длинные из всех языков?
У нас в свое время для этих целей писали специальную утилиту, которая проходила по всем ресурсам и искала отличия. Задачка не из легких. Но стоит один раз написать такую утилиту и проблема пропадает. Возможно в сети есть аналогичные утилиты.
__________________________________
Василий Черневич (aka Willi)
AfxSetResourceHandle(<хендл только что подгруженной языковой DLLки>);
заменит весь текст написанный тут.
Какой вообще прикол забирать под себя LoadString и прочее...
FindResource все сделает сам, в том числе возвратит ресурс с языком LANG_ENGLISH, SUBLANG_DEFAULT
или, если и его нет, просто вернет самый первый ресурс с запрошенным идентификатором.
Хотелось бы также узнать, откуда взялось столь категоричное заявлеие, что LoadString
принципиально не поддерживает работу с таблицами строк на нескольких языках.
Только не FindResource, а FindResourceEx. И именно это функция используется в примерах к статье для поиска ресурса на нужном языке.
А в чём, по Вашему, заключается поддержка таблиц строк на нескольких языках с LoadString? Если мы не можем задать (при вызове фнкции) язык, для которого мы хотим загрузить строку, то о чём тут говорить? А если Вы хотите сказать, что LoadString способно безошибочно работать в рпиложении, в ресурсах которого есть несколько таблиц строк, то тут Вы правы.
LoadString, LoadIcon, LoadMenu, LoadCursor и др. используют FindResource для поиска нужного ресурса.
FindResource использует алгоритм поиска по языку с откатом на LANG_ENGLISH,SUBLANG_DEFAULT в случае
отсутствия языкового ресурса отличного от LANG_NEUTRAL и языка текущей локали.
В MSDN алгоритм поиска по языку функциями FindResource и FindResourceEx был опубликован два года назад.
Но. Во-первых, это была прямая дезинформация, во-вторых, уже год как той статьи нет. Я писал тестовую
программу для изучения этого вопроса.
Все дело в том, что NT-подобные системы поддерживают ф-ю SetThreadLocale, которая назначает язык текущему потоку. После ее вызова ф-ии типа LoadString, LoadMenu и т.д. вернут ресурс на языке, который является текущим для данного потока.
В искустве летать есть один маленький секрет. Секрет этот в том,чтобы бросить себя изо всех сил на землю — и не попасть. Выберете погожий денек и попробуйте сами.
Для MFC версии есть проблема с диалоговым окном: DoModal не работает с CreateIndirect() :-( Работает только с InitModalIndirect(), но этот метод не локализует ресурс. Как быть?
Особенно с LoadStringLang. Её реализация в Windows действительно очень далека от идеала.
Единственное, что с картинкой, показывающей заполненность групп по 16 строк. Надо было нолики подрисовать и в начале группы, чтобы не пугаться лишний раз, когда первый раз видишь это дело под отладчиком :)
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Здравствуйте, Михаил А. Русаков, Вы писали:
МАР>Замечание относится непосредственно к MFC. Итак, локализация в MFC делается еще проще: достаточно в редакторе ресурсов приложения выбрать ресурс, подлежащий локализации, кликнуть правой кнопкой мыши и выбрать пункт "Insert Copy". После этого Вас попросят указать язык, на котором будет эта копия. Далее остается только перевести скопированный ресурс — остальное MFC сделает сама.
МАР>При загрузке приложения MFC будет искать и загружать ресурсы в зависимости от текущей локали пользователя в следующей последовательности: сначала ищет ресурс с суб_языком = равном локали Windows. Если такового не находится, то она (MFC) пытается загрузить ресурс с языком = локали Windows... Затем английский, затем нейтральный... И если уж не нашлось никакого, то MFC берет ресурс с первым "попавшимся под руку" языком.
МАР>Смысл в том, что у программиста не болит голова о локализации... Знай себе, вовремя делай копии ресурсов...
МАР>Единственным недостатком предложенного мной метода является то, что MFC грузит ресурсы язык которых соответствует языку Windows, а не региональным настройкам панели управления. То есть, если Вы будете работать на английском Windows, то всегда будете наблюдать английский интерфейс, если локализация выполнена по описанному мной способу.
МАР>С другой стороны, это правильное решение: ведь если Вы работаете в ОС на каком-то языке, то это значит, что Вы можете, как минимум, читать на этом языке....
МАР>Удачи!
Помнится, что 'такой автомат' работает только под ВинНТ 4.0 а в остальных случаях надо руцями пользовать FindResourseEx...
C уважением.
Здравствуйте, Коваленко Дмитрий, Вы писали:
КД>Спасибо, сэкономили кучу времени.
КД>Особенно с LoadStringLang. Её реализация в Windows действительно очень далека от идеала.
КД>Единственное, что с картинкой, показывающей заполненность групп по 16 строк. Надо было нолики подрисовать и в начале группы, чтобы не пугаться лишний раз, когда первый раз видишь это дело под отладчиком
Если хочется, чтобы выбор языка происходил на основании именно "настроек в панели управлния", т.е. locale, то можно сделать так:
BOOL CMyApp::InitInstance()
{
InitCommonControls();
HINSTANCE hRes = NULL;
LANGID li = GetUserDefaultLangID();
switch(PRIMARYLANGID (li)){
case LANG_RUSSIAN:
hRes= LoadLibrary(L"Resource_Russian.dll");
break;
case LANG_SPANISH:
hRes= LoadLibrary(L"Resource_Spanish.dll");
break;
case LANG_ITALIAN:
hRes= LoadLibrary(L"Resource_Italian.dll");
break;
case LANG_GERMAN:
hRes= LoadLibrary(L"Resource_German.dll");
break;
case LANG_FRENCH:
hRes= LoadLibrary(L"Resource_French.dll");
break;
};
if(hRes)
AfxSetResourceHandle(hRes);
CWinApp::InitInstance();
...
Соотвественно в dll-ках лежат копии ресурсов, с нужным language.
У меня в самом exe-шнике лежит английская версия, которая грузится по умолчанию, либо если не получилось загрузить нужную библиотеку.
Здравствуйте, Гулай Борис aka BoresExpress, Вы писали:
А зачем такие сложности с этими устаревшими таблицами сообщений, для которых нужно отдельно запускать mc? В MFC можно все поместить в обычные строковые ресурсы, а потом форматировать строку, содержащую всякие там %1 и %2!d! с помощью CString::FormatMessage(messageId [, parameter]...).
Здравствуйте, Михаил А. Русаков, Вы писали:
МАР>Единственным недостатком предложенного мной метода является то, что MFC грузит ресурсы язык которых соответствует языку Windows, а не региональным настройкам панели управления. То есть, если Вы будете работать на английском Windows, то всегда будете наблюдать английский интерфейс, если локализация выполнена по описанному мной способу.
Действительно всегда, или, всё же, существует способ обмануть MFC?
И если существует, еще усложню — есть ли способ менять язык во время работы программы?
Здравствуйте, e-smirnov, Вы писали:
ES>Действительно всегда, или, всё же, существует способ обмануть MFC? ES>И если существует, еще усложню — есть ли способ менять язык во время работы программы?
Я как раз на эту тему вчера статью отослал, так что если опубликуют...
Здравствуйте, Kooksha, Вы писали:
K>Для MFC версии есть проблема с диалоговым окном: DoModal не работает с CreateIndirect() Работает только с InitModalIndirect(), но этот метод не локализует ресурс. Как быть?
Очень просто. Для вашего класса диалога CMyDialog нужно вызывать пустой унаследованный конструктор CDialog(), например, вот так:
CMyDialog::CMyDialog(CWnd* pParent /*=NULL*/) : CDialog()
{
m_pParentWnd = pParent;
//{{AFX_DATA_INIT(CMyDialog)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
}
Ссылку на родительское окно сохраняем в конструкторе, нам она понадобится в вызове метода InitModalIndirect. Ну, а теперь переопределяем метод DoModal, например, так:
int CMyDialog::DoModal()
{
if (!InitModalIndirect((LPDLGTEMPLATE)LoadResourceLang(RT_DIALOG, this->IDD), m_pParentWnd))
return IDCANCEL;
return CDialog::DoModal();
}
где LoadResourceLang — своя функция, которая, собственно, описывает логику поиска сначала ресурсов на русском, например, а затем и на английском.
Алексей Радиванюк
Re[2]: MFC: есть проблема с диалогом (DoModal)
От:
Аноним
Дата:
09.12.04 09:10
Оценка:
Здравствуйте, Alecard, Вы писали:
A>Ссылку на родительское окно сохраняем в конструкторе, нам она понадобится в вызове метода InitModalIndirect. Ну, а теперь переопределяем метод DoModal, например, так:
A>
A>int CMyDialog::DoModal()
A>{
A> if (!InitModalIndirect((LPDLGTEMPLATE)LoadResourceLang(RT_DIALOG, this->IDD), m_pParentWnd))
A> return IDCANCEL;
A> return CDialog::DoModal();
A>}
A>
К сожалению, InitModalIndirect() отказывается создавать модальный диалог на основе указанного шаблона. Порывшись в Google, я обнаружил такое решение: подменить всю функцию DoModal(). См. http://www.developpez.net/forums/viewtopic.php?p=768010.
Единственное, что я поменял — вместо:
К сожалению, вынужден сказать, что предлагаемый подход не работает в полной мере.
Например, для немецкого языка диалоги не показывают умляуты, хотя в меню они есть.
Как это дело проверить
1. Выставляем текущую локаль — германия
2. Запускам тестовое приложение — убеждаемся, что в меню умляуты есть, в диалогах (например, в эбаут) — нет.
3. Выставляем язык для неюникодных приложений — немецкий.
4. Запускаем тестовое приложение, убеждаемся, что умляуты появились и в диалогах.
Итого — все более-менее нормально работает только при _обязательной_ настройке для неюникодных приложений. А нафига оно тогда, спрашивается, в таком виде надо... Вообще говоря, странно — в меню умляуты есть всегда, в диалоге — нет. Мысли по поводу?