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

Подмостки для Вавилонской башни, или О собственных типах данных для многоязычных приложений

Время на прочтение14 мин
Количество просмотров6.2K
Всего голосов 16: ↑14 и ↓2+12
Комментарии14

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

А что делать если я хочу перевести: «Should contain X character(s)»? где Х — неопределенное число, которое задается позже?.. в зависимости от >1 или 1 будут разные переводы. И если в русском и английском переводе в этом случае будет всего по два соответствующих варианта, то в других языках и других предложениях, количество возможных переводов может отличаться.

Хороший вопрос.


Microsoft вводит требование локализуемости, т.е. отделение кода и ресурсов. Утверждается, что в локализуемом приложении не понадобится писать код для новых языков приложения. Наивные.


В вашем примере это явно не так. Возможно, именно для таких случаев, необходимо уметь размещать в сателлитных сборках (один из видов хранения ресурсов в .NET) стратегии по обработке конкретных ресурсов для разных языков.

мало что понял из вашего ответа. лексикон .net совершенно не знаком. Но из ответа я понял что никак.
Но почему бы вам не реализовать ICU Messages? (если еще нет соответвующей либы)

Сейчас никак — стандартных средств нет.


Но и случаев таких пока не было.

Это проблема любой системы перевода (не относится к решению предлагаемому в статье). Проще всего в локализируемую строку поместить токен, который программа подменяет на значение в run-time, в простейшем случае можно тупо использовать "{0}" и string.Format(localizedString, someValue), хотя вы понимаете, чем это чревато.

А по-поводу статьи (далее обращаюсь к ТС), извините, но вводить новый тип для строк и использовать что-то вроде

var fu = new MultiCulturalString(ru, "Шоколад Алина").SetLocalizedString(en, "Chocolate Alina");

вообще не круто. Интересно будет посмотреть как это выглядит в конце. Плюс вы не привязываетесь ни к winforms, ни к wpf (ни к чему-то еще?). А это как раз представляет больший интерес, чем простая возможность хранить строки для MessageBox.

В данный момент мы используем решение, где локализируемые строки выглядят как простой класс с «константами»:

    public static class Lng
    {
        public static class Login
        {
            public static string Disconnect { get; private set; } = "Disconnect";
            ...
        }
        ...
    }

а «локализация» происходит за счет рефлексии. Для WPF это легко обернуть в MarkupExtension где нужные строки возвращаются по ключу (ключ — это путь к свойству, к примеру Login.Disconnect).

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

Возвращать null на неизвестную строку? Боже упаси! Если строка отсутствует в файле локализации, то вернется значение по умолчанию (в нашем случае это псевдо-english, который по-хорошему следует локализовать в en-US/en-GB). Если неверен ключ, но вернется значение ключа "!!!" + key (в этом случае пользователь/тестер сможет оперативно сделать багрепорт, а программист — с легкостью найти ошибку).
А по-поводу статьи (далее обращаюсь к ТС), извините, но вводить новый тип для строк и использовать что-то вроде
var fu = new MultiCulturalString(ru, "Шоколад Алина").SetLocalizedString(en, "Chocolate Alina");

На практике такой код практически не встречается, ведь ресурсы динамически подгружаются из упомянутого CustomizedResourceManager. Именно он нужен, например, для WinForms.


Возвращать null на неизвестную строку? Боже упаси!

Совершенно согласен. Это предположение введено в середине статьи, чтобы развить тему с IResourceFallbackProcess позже. А потом сказано, что пустоты на UI, конечно же, недопустимы.

Поясню. Есть две задачи:


  • На UI отобразить хоть что-то адекватное
  • Уметь программно понять, что перевода на заданный язык нет. Для этого нам и нужны методы GetString(...).

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

Интересно, а какие именно методы "победили"?


У нас многоязычная строка и обвязка живут видимо потому, что существуют не сами по себе, а тесно интегрированы с другими инфраструктурными классами/библиотеками, включая ORM.

Для Вавилонской башни, как мне кажется, больше подходит понятие «фундамент», а не «подмостки».

Ну мы старались не привязывать Multistring к чему-либо, а использовать именно как отдельный тип данных.


Видели два сценария использования этого типа.


Первый: тип данных для пользовательских данных, которые сам пользователь и наполняет (сделали удобные UI-компоненты для работы с типом).
Примеры:


  • Тэги. Хозяин блога хочет добавить тэги к тексту (book = книга). Без Multistring так и добавляли: #book, #книга
  • есть какая-то сущность с предопределёнными атрибутами. Например атрибут "размер" (=size). Без Multistring пользователь не мог добавить перевод с русского на английский.

В обоих случаях идея провалилась по той причине, что пользователи никак не могли понять концепции нескольких строк "в одной". Были бы программистами — скорее всего поняли бы, но мы работаем с простыми людьми — они не поняли.
В результате для блогов оставили как было, для атрибутов — используем стандартные структуры данных, добавляем таблицы, и т.д. — зависит от случая.


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

Что-бы повысить удобство использования, рекомендую вам отказаться от .ToString() в той концепции, в которой вы его используете, а именно для получения текущего значения (по текущей культуре). Всё-таки .ToString() — это метод, описывающий объект, и подразумевается, что его результат от версии к версии может быть разным. Заменить .ToString() следует операторами, конвертирующими в/из System.String.


P.S. выкидывать сам .ToString() — не обязательно

По поводу использования операторов и переопределенного Object.ToString() — спасибо, принято!


Все же методы ToString()/GetString() использовать приходиться, по нескольку перегрузок каждого с нужными параметрами (локаль, использовать ли и какой именно fallback process, поставщик форматирования). Причем две перегрузки ToString() нужны для корректного форматирования при подстановках.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий