Локализация ASP.NET MVC приложений

    На тему локализации уже было несколько статей на Хабре, например Локализация ASP.NET MVC приложения с помощью БД или MVC 2: Полное руководство по локализации, но все-таки проблема до сих пор актуальна.

    Совсем недавно у нас возникла задача локализации (перевода) интерфейса сайта на другой язык. Наш проект разрабатывается на ASP.NET MVC и в проекте достаточно много клиентского кода на JavaScript и шаблонах jQuery. Еще одним важным моментом для нас является возможность интерактивной работы переводчиков, фактически перевода сайта на лету.

    В основном все статьи, касающиеся этой темы, предписывают использовать файлы ресурсов, при этом решение получается не гибким, громоздким, и к тому же, никак не учитывается проблема перевода скриптов и шаблонов, которые в любом современном веб-проекте составляют значительную часть. В конечном итоге мы решили написать свою библиотеку Knoema.Localization, которую недавно выложили в открытый доступ. Наш код базируется на замечательной разработке Griffin, но был существенно усовершенствован, включает интерактивный инструмент для перевода, а также средства естественной локализации JS кода и шаблонов.

    Возможности

    Библиотека позволяет локализовать:
    • Представления (views)
    • Строковые константы в коде
    • Модели и текст в атрибутах валидации данных
    • Текст в коде на JavaScript
    • Шаблоны jQuery

    Основная идея заключается в том, что все локализуемые строки определяются именем файла или классом, а затем представляются в таком виде, в котором они находятся в проекте.


    Подключение и конфигурация

    Для подключения библиотеки к проекту достаточно установить Nuget пакет Knoema.Localization.Mvc. Для хранения локализованных данных вам также потребуется реализация репозитория. В простейшем случае достаточно подключить готовую реализацию для EF установив пакет Knoema.Localization.EFProvider. Альтернативным вариантом является разработка своего провайдера путем реализации достаточно простого интерфейса ILocalizationRepository.

    В Global.asax в Application_Start() инициализировать созданный репозиторий:
    Knoema.Localization.LocalizationManager.Repository = new Knoema.Localization.EFProvider.LocalizationRepository();
    

    и в методе RegisterRoutes прописать:
    routes.IgnoreRoute("_localization/{*route}");
    

    Если вы используете Nuget, то при добавлении сборок в web.config автоматически добавятся следующие значения:
     <system.webServer>  
    		<modules>
    			<add name="Knoema.Localization" type="Knoema.Localization.Web.LocalizationModule"/>
    		</modules>
    		<handlers>
    			<add name="Knoema.Localization" verb="*" path="_localization/*" type="Knoema.Localization.Web.LocalizationHandler" allowPathInfo="true" resourceType="Unspecified" />
    		</handlers>
    	</system.webServer>
    

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

    Локализация представлений

    Для локализации представлений в web.config нужно указать, что в качестве базового класса для страниц будет использоваться LocalizedWebViewPage:
     <system.web.webPages.razor>
        ...
        <pages pageBaseType="Knoema.Localization.Mvc.LocalizedWebViewPage">
          <namespaces>
            <add namespace="System.Web.Mvc" />
          ...
          </namespaces>
        </pages>
      </system.web.webPages.razor>
    

    После этого, в коде представления станет доступен Html helper:
    public string R(string text, params object[] formatterArguments);
    

    Пример

    <p>@R("Hello world!")</p>
    

    Текст можно параметризовать:
    <p>@R("Hello {0}!", username)</p>
    

    Локализация строковых констант в коде

    Строки в коде локализуются с помощью расширения:
    public static string Resource(this string value, object obj)
    

    или, если метод статический, где нет объекта this, можно использовать:
    public static string Resource(this string value, Type type)
    

    Пример

    "The given input does not refer to a valid Search.".Resource(this)
    

    Локализация модели

    Для локализации моделей необходимо переопределить стандартный провайдер валидации и провайдер метаданных. Для этого в Application_Start() в Global.asax добавить:
    ModelValidatorProviders.Providers.Clear();
    ModelValidatorProviders.Providers.Add(new ValidationLocalizer());
    ModelMetadataProviders.Current = new MetadataLocalizer();
    

    Все модели, которые могут быть локализованы, должны быть помечены атрибутом Localized.

    Пример

    [Localized]
    public class SignInViewModel
    {
    	[Required(ErrorMessage = "Please provide your e-mail")]
    	[Display(Name = "E-mail")]
    	public string EMail { get; set; }
    
    	[Required(ErrorMessage = "Please type your password")]
    	public string Password { get; set; }
    }
    

    Свойства и атрибуты отдельно локализовывать не нужно. Все метаданные добавятся автоматически, останется только добавить для них соответсвующий перевод.

    Локазиция javascript и html шаблонов

    Строковые константы в javascript коде локализуются с помощью расширения JQuery:
    $.localize(text, scriptSource);
    

    Расширение можно подключить в любом месте сраницы с помощью хелпера, лучше для всего приложения сразу:
    @RenderLocalizationIncludes(User.IsInRole("Admin"))	 
    

    Кроме того, RenderLocalizationIncludes встроит на сайт виджет (при условии, что вы администратор), где вы сможете перевести все, что локализовали.

    Пример

    $.localize("Layout options", "~/js/shared/site.js");
    

    Примечание

    Нам показалось неудобным, что каждый раз необходимо указывать имя файла. В нашем проекте мы используем минификатор ресурсов Cassette, где есть возможность добавить обработчик ресурсов, перед тем как они скомпилируются. Простой пример конфигурации:
    public class CassetteConfiguration : ICassetteConfiguration
    {
    	private void CustomizeScript(ScriptBundle bundle)
    	{
    		bundle.Processor = new ScriptPipeline().Prepend(new LocalizationResourceProcessor());
    	}
    
    	public void Configure(BundleCollection bundles, CassetteSettings settings)
    	{
    		bundles.AddPerIndividualFile<ScriptBundle>("js/shared/site.js", customizeBundle: CustomizeScript);
    	}
    }
    

    Теперь длинный вызов $.localize(«Layout options», "~/js/shared/site.js") можно заменить на:
    __R("Layout options");
    

    LocalizationResourceProcessor заменит __R на $.localize и добавит вторым параметром имя файла.

    Добавление перевода

    Перевод можно добавлять прямо на сайте с помощью виджета, который появится в левом нижнем углу экрана.

    Серым цветом выделены те представления или модели, которые полностью переведены.

    Редактирование:


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


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

    Ссылки:
    Исходный код на GitHub
    Knoema.Localization.Core
    Knoema.Localization.MVC
    Knoema.Localization.Cassette
    Knoema
    0.00
    Company
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 75

      0
      А проблем не возникало из-за того, что ключом служит строка? Ведь есть варианты, когда одна и та же английская строка переводится на русский по разному в зависимости от контекста.
        0
        Ключом является имя файла или класса, в котором находится строка. Да, вероятность того, что одно и тоже слово встретится много раз и будет переводится по разному конечно же есть, но пока не возникало такой проблемы. Кстати если слово уже где то встречалось, то есть возможность выбрать из похожих уже добавленных переводов.
          0
          А как возникнет — что делать будете? Я вот сталкивался уже с такой проблемой (правда дело было давно и на PHP). В итоге в представлениях строки заменили на строковые идентификаторы. Ну и второй момент — опечатки в строках (на экране и в ресурсах строка по разному) будут заметны только в run-time.
            0
            Если возникнет, в крайнем случае можно заменить слово синонимом. А если серьезно, то возможно вы и правы. Мы достаточно быстро таким способом локализовали огромное количество кода, скриптов и представлений, так что подход пока вполне себя оправдывает. По поводу опечаток, которые видны только в run-time не совсем поняла. Если в коде опечатка, то ее все равно придется исправить именно в коде. А если ошибка в переводе, в run-time ее можно исправить в виджете перевода строк.
              0
              Ваш пример:
              «The given input does not refer to a valid Search.».Resource(this)
              а в файл перевода случайно попадет
              The given input doesn't refer to a valid Search.
              Ну или Saerch вместо Search. И все — несовпадение. Кроме того — длинная текстовая строка даром не нужна в исходном коде — только место занимает.

              По сути, ваши строки это аналог Id, но только зачастую очень длинный и без проверки на этапе компиляции. Например, если сравнивать с решением через ресурсы — там ошибка в id строки просто не пройдет компиляцию (т.к. у прокси-класса не будет такого свойства). Да и IntelliSense помогает.

              Ну и про разный перевод одних слов — я быстро вышел на то, что банальный Ok зачастую надо переводить по разному в разных диалогах. В том проекте пришлось менять строки на Id. Кроме того, и исходный код стал читабельнее.
                0
                Кроме того — длинная текстовая строка даром не нужна в исходном коде — только место занимает.

                Это просто пример. Никто не мешает поместить эту строку в файл ресурсов и написать:
                Resources.InvalidInput.Resource(this)
                

                В том проекте пришлось менять строки на Id. Кроме того, и исходный код стал читабельнее.

                По поводу замены строк на id развернулась дискуссия в комментариях ниже. Если интересно, можете почитать. Лично я сомневаюсь в том, что замена всего текста идентификаторами сделает код читабельнее. Кроме того, каждый раз «возиться» с словарями ресурсов на мой взгляд достаточно утомительно.
                Представьте, что вам нужно локализовать 100 разных строк. Это значит, что надо сделать много однообразных действий: открыть файл ресурсов, добавить туда значение потом заменить текст на соответствующую ссылку. В реальном же проекте таких строк не 100, а гораздо больше.
                  0
                  Это значит, что надо сделать много однообразных действий: открыть файл ресурсов, добавить туда значение потом заменить текст на соответствующую ссылку.

                  Для однообразных действий придумали автоматизацию. Например, Reshaper с таким прекрасно справляется.
                    0
                    Еще бы он бесплатно с этим справлялся и было бы совсем замечательно.

                    На мой взгляд прежде чем пытаться что-то автоматизировать нужно все-таки подумать, а нельзя ли без этого обойтись вообще. В любом процессе отсутствие необходимости выполнения какого-либо шага предпочтительнее его автоматического или автоматизированного выполнения. Оккам не зря про бритву придумал
                    0
                    Читал. Я за Id, т.к. появляется контроль на этапе компиляции. Кроме того, мне удобнее видеть Resource.SomeMessages.CopyrightNotice чем 2-3 строки текста с копирайтами. Как правило тексты содержат информации больше, чем надо программисту. Поэтому давайте так, чтобы каждому свое.

                    Ну и ответили уже — можно автоматизировать однообразные действия, а не изобретать с нуля велосипед.

                      0
                      Мне кажется, что мы говорим о разных вещах. Ваши рассуждения о контроле на этапе компиляции применимы к C# коду и частично представлениям. Поясните пожалуйста что Id дают для кода на JS
                        0
                        JSLint?
                          0
                          JSLint — это всего лишь статический анализатор. Возможно я и ошибаюсь, но он не отловит обращения к misspelled свойствам объектов. Или я не прав? Поделитесь практическим опытом использования JSLint в связке с локализацией через ресурсы если таковой имеется.
                            0
                            Возможно я и ошибаюсь, но он не отловит обращения к misspelled свойствам объектов. Или я не прав?

                            Опять-таки, для начала можно рассматривать Id как константы, а не свойства. У нас будут проблемы с пространствами имен, но зато мы сразу отловим несовпадения.

                            Во-вторых, статический анализ с равным успехом позволяет отследить разобрать конкретный объект на объявленные/необъявленные свойства (нет, JSLint этого не умеет, и нет, я сам такого не делал, но с точки зрения обработки это достаточно тривиально).
                              0
                              А можно поинтересоваться как вы локализуете JS-код и шаблоны в своем проекте? В инете как ни странно очень мало информации на эту тему.
                                +1
                                Если вкратце, мы не локализуем js-код, потому что все возможные сообщения стараемся передавать с серверной стороны. Чисто клиентских шаблонов у нас просто нет.
                                  0
                                  Ясно. Вполне разумный подход. Мы начали переходить на клиентские шаблоны, чтобы уменьшить количество обращений к серверу и ускорить многие довольно элементарные операции убрав обращения к серверу вообще
                                    +1
                                    Типичный tradeoff поддерживаемости против скорости.
                          0
                          1) Почему «частично представлениям». Они вполне перекомпилируются.

                          2) В самой статье речь идет не только о JS, поэтому не надо сводить разговор только к JS.

                          Далее, необходимо определиться как построен JS код. Например, для валидации вполне можно передавать тексты сообщений из ресурсов.
                            0
                            1) По умолчанию представления в ASP.NET MVC проектах не компилируются при сборке

                            2) Прочитайте пожалуйста внимательнее вводную к статье и основные причины побудившие нас создать библиотеку:
                            «В основном все статьи, касающиеся этой темы, предписывают использовать файлы ресурсов, при этом решение получается не гибким, громоздким, и к тому же, никак не учитывается проблема перевода скриптов и шаблонов, которые в любом современном веб-проекте составляют значительную часть»

                            Мы предлагаем комплексное решение для веб-проектов на ASP.NET MVC с большим количеством JS-кода. В серверном коде вполне можно обойтись стандартными ресурсами. Вообще это не я разговор к JS свожу, а скорее вы от него уходите
                              0
                              1) Не аргумент. Главное что включается и это штатная возможность.

                              2) Про шаблоны уже сказал. Представления в статье описаны — тоже сказал. Про JS уточнил выше. Итого, в серверном коде и представлениях можно использовать ресурсы, про JS — смотря как организован сам JS код. В некоторых ситуациях опять же хватит ресурсов.
            0
            2012 год. Время фреймворков. И вот, продукт одной из крупнейших софтварных компаний не может вменяемо справиться с локализацей без сторонних библиотек.
              0
              Проблема достаточно общая. Значительная часть кода в проекте это скрипты и JS-шаблоны, которые от серверного фреймворка никак не зависят. Действительно удобных инструментов локализации JS для разработчиков мы так и не нашли. Пришлось сделать свой. Будем признательны если подскажете альтернативы
                0
                Я никоим образом не намекал на ненужность вашей разработки, просто удивился, что так обстоят дела. Я очень рад, что есть люди, решающие эти проблемы для (и за) всех остальных, за это вам огромное спасибо.
            • UFO just landed and posted this here
                0
                Вы серьезно предлагаете все скрипты и JS-шаблоны держать в ресурсах? Переводить их целиком? Как их потом поддерживать при изменениях и командной разработке? В нашем проекте тысячи строк в скриптах и шаблонах если что.
                +1
                Моя практика показывает, что даже привязка к имени файла не является обязательной (как это ни удивительно, но конфликтов практически не возникает в этом случае). Достаточно только ключа. Более того, часто такие фразы как «Сохранить» или «Поле должно быть заполненно» являются общими для всех файлах, их удобно держать в единственном экземпляре. В моем случае виджет редактирования перевода, появляющийся на странице, просто знает, какие ключи были на этой странице сейчас задействованы, их и дает редактировать.
                  0
                  Имя файла все-таки нужно. У нас базовый язык английский и зачастую бывает что одно и то же слово является и существительным и глаголом. В этом случае без контекста совсем не обойтись. Простой пример — это слово map, которое может означать «карта», а может «отобразить/привязать»
                    +1
                    Ну так ключ же не обязательно называть «map». Можно его назвать «to_map», например, или еще как-то, чтобы подчеркнуть, что это глагол. Мне кажется, имя файла может являться подсказкой (как в известной системе gettext), но это все-таки не часть ключа. И о как быть при рефакторинге, когда часть кода/шаблона вместе со всеми ключами переезжает в другое место?
                      0
                      У нас система заточена на то, что переводимое слово или фраза сами по себе являются ключом. Это очень удобно при разработке. Не нужно вводить каких-то идентификаторов, запоминать их. Мы просто обрамляем все строки подлежащие локализации вызовом функции, как показано в примерах выше. При этом код остается хорошо читаемым и легко поддерживаемым. Добавление в ключ имени файла позволяет решить вопрос с коллизиями, а также позволило сделать удобный виджет для перевода с группировкой всех строк по папкам и файлам.

                      Проблема рефакторинга имеется, строки фактически нужно заново переводить, но этот процесс существенно облегчен автоматическими подсказками. Система сама предлагает вариант перевода для уже имеющихся слов или фраз. Фактически надо только мышкой прощелкивать строки.
                        0
                        Ну да, вы используете ту же идеологию, что в gettext. Сама фраза и является ключом (только в gettext нет завязки за имя файла, а у вас есть). Это все хорошо, но только вот как быть, когда находятся ошибки в английском варианте, или же вообще английский вариант заменяется на другой, не очень понятно. Вроде в poedit (утилита редактирования словарных файлов для gettext) есть режим поиска с неточным совпадением, но как оно работает на практике, мне пока не довелось попробовать.
                          0
                          Мне ближе концепция, что ключом является строковый литерал, суррогатный идентификатор, но не сами слово или фраза. Имя файла/класса для переводчика не даёт контекст, особенно если это файл/класс не контроллера или вью, а какого-нибудь хелпера или сервиса. В литерале мы вольны описать контекст сколь угодно полно без влияния на представление на основном языке, с одной стороны, а с другой переформулировка или исправление опечатки в ключе не несёт необходимости привязывать существующие переводы к новому ключу.
                            0
                            А если таких строковых литералов несколько сотен (соответственно ключей надо генерировать столько же), а опечаток не сравнимо меньше? Я думаю, что это достаточно обременительно для каждой строки указывать ключ, к тому же читаемость кода сильно ухудшается.
                              0
                              *несравнимо
                                0
                                В качестве ключа вполне может выступать полноценная фраза и тогда читаемость не ухудшится. Главное чтобы для основного языка выводилась не она, а ассоциированное с ней значение. На первом этапе для него это может быть копипаста, а затем мы сможем изменять значение отдельно от ключа.
                                  +1
                                  Да, лично я такой вариант как раз предпочитаю. Берем исходную фразу на английском, переводим в нижний регистр, убираем знаки препинания, артикли, предлоги, и другие вспомогательные слова, сокращаем очевидные слова, а пробелы заменяем на "_". Например, «Enter your login and password» превратится в «Enter_login_pass» (с большой буквы ключ, т.к. и сама фраза начинается с большой буквы, довольно удобная мнемоника). Читаемость кода от таких ключей почти не страдает, понять, что они означают при переводе — легко (даже если без виджета переводится). И даже очень длинные фразы и целые абзацы превращаются в ключи самый максимум длиной 50 символов.
                                    0
                                    Что-то подобное и имел в виду.
                                    0
                                    Что-то в вашем варианте есть, более того с использованием нашей либо такой вариант очень легко реализовать если базовый язык рассматривать как нейтральный, а не английский к примеру.

                                    Но нам пока хватает английского как базового, удобства перевешивают недостатки. В конечном итоге все определяется трудозатратами на начальный перевод и его дальнейшее сопровождение. Пока я считаю в нашем проекте баланс оптимальный.
                                      0
                                      Именно, ключи составляются на нейтральном языке понятном разработчикам («Видел я ваш английский — в нём почти все слова из C++!»), а «литературный» перевод делают профессионалы в языках, не трогая код.
                                        0
                                        Так на вашем же скриншоте суррогатные ключи есть: Host_DisplayName, Email_DisplayName и т.д.
                                        habrastorage.org/storage2/a3a/1db/add/a3a1dbadd3da413dadfe7d2cb2350ebb.png
                                          0
                                          Это не совсем ключ. В данном случае это свойство модели (или атрибут свойства), которое локализуем.
                          0
                          А как понять, что это глагол?
                            0
                            При проектировании локализации разработчики часто упускают один очень важный момент: у локализации через ресурсы, несмотря на все ее недостатки, есть одна киллер-фича--поскольку это долгое время было рекомендованным решением от MS, вендоры локализации поддерживают формат resx, и требуют намного меньше денег за перевод, если вы им отправляете весь текст в файлах ресурсов.

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

                            Так вот, эти тулзы не поддерживают никакие кастомные файлы, они поддерживают .resx. Лично мне тоже очень не нравится локализация через satellite assemblies (она отвратительна, в общем-то), но на крупных проектах, где локализация стоит очень дорого, resx--единственный выбор.

                            Правда, можно спроектировать систему так, что в приложении локализация происходит не через satellite assemblies, но сами тройки «ключ-оригинал-перевод» хранятся в resx. Или можно сделать двухступенчатый процесс: локализацию хранить как угодно (хоть в базе), и написать конвертер в resx для переводчиков, и обратный конвертер, чтоб импортировать перевод назад в базу.

                            Так или иначе, не забывайте про стандарты отрасли локализации!
                              0
                              Я нежно люблю resx и знаю всю подноготную промышленной локализации :-) Все-таки много лет руководил разработкой продукта, который переведен на 7 языков, включая китайский, и содержит тысячи фраз для локализации. И надо признать, что для современных веб-проектов локализация через стандартные resx-ресурсы это полный геморрой и извращение. Текущие проекты требуют новых подходов и решений.

                              В нашей библиотеке реализован стандартный экспорт-импорт через JSON-файл. Очень удобно и для обмена переводами и для взаимодействия с переводчиками. Можно реализовать и поддержку банального CSV, с ним точно проблем не будет поскольку даже MS свои стандартные ресурсы выдает в CSV.
                                0
                                Вопрос на засыпку. Каким образом не через задницу прикрутить resx для локализации файлов JS-скриптов? У нас в проекте это добрая половина кода.
                                  0
                                  А разве нет конвертеров из ABC в resx и обратно (ABC — любой удобный вам формат)? И разве сложно такой конвертер написать?
                                    0
                                    Отвечать вопросом на вопрос не есть хороший тон :-) К тому же встречный вопрос не имеет отношения к оригинальному.

                                    Написать конвертер можно в какой угодно формат. Вопрос только зачем? Объясните плиз по шагам как использовать resx-ресурсы для локализации JS-скриптов.
                                      0
                                      Расскажу о своем опыте: в силу исторических причин JS ресурсы на проекте были локализованы в JS-файлах (через json, обычные словари через нативные яваскриптовые объекты)--это вполне хороший подход. В силу других исторических причин на проекте было много .resx ресурсов. Кроме того, (как я писал, это правильно) было принято решение вендорам локализации отправлять .resx.

                                      Поэтому мы использовали такой подход:

                                      1. переведенные файлы хранили в svn
                                      2. на build сервере, в рамках continuous integration, во время билда все .resx файлы копировались в специальную папочку
                                      3. все js файлы конвертились в resx и складывались в эту же папочку
                                      4. в процессе билда для всех переводов, что лежат в svn те resx файлы, что получены из js, конвертились обратно в js и складывались в релизную папку
                                      5. все остальные resx файлы собирались в сателлитные сборки (автоматически, на билд сервере, по скриптам, сгенерированным автоматически на основе файлов проекта)

                                      То есть для JS схема такая: JS->[build]->.resx->[localize]->localized resx->[send to developers, add to svn]->[build]->JS
                                        0
                                        Схема рабочая, тут вопросов нет, но на мой взгляд сложноватая, хотя возможно в вашей ситуации с учетом «исторических причин» и самая оптимальная. У нас нет всех этих промежуточных шагов и все вопросы связанные с локализацией отделены от сборки проекта. Исправления в локализации и перевод новых строк можно делать фактически на лету. Мы вносим исправления и новые строки в staging-среду, а потом импортируем на продакшн. Все очень динамично и легко. Изменения сразу видны на сайте и их легко контролировать. В случае resx-файлов важная информация о контексте для переводчиков фактически недоступна (кроме ключа ресурса).

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

                                        Я думаю что мы просто тестовый проект выложим в публичный доступ, чтобы желающие могли попробовать и сами почувствовать разницу.
                                    +1
                                    Ну, off the top of my head могу предложить обработчик запросов, который будет по фиксированному маршруту отдавать JSON, содержащий локализованные строки, а к тем уже обращаться через глобально доступный хелпер.

                                      0
                                      Такая схема на большом проекте будет чувствительна к человеческим ошибкам и не очень удобна. Надо добавить строку в ресурс в одном файле, потом ее заиспользовать совсем в другом при этом не ошибившись с ключом. Сам JS-код, особенно при большом количестве строк, становится не очень читаемым. Вместо понятных строк на английском — ключи ресурсов. Разработчику чтобы посмотреть реальный текст и возможно исправить его нужно лезть в ресурсы и искать соответствующую строку. Тот кто работал с большими файлами ресурсов в VS знает какой это адъ.
                                        0
                                        Простите, а какая схема не будет чувствительна к человеческим ошибкам?
                                          0
                                          Идеальных решений не бывает. Бывают более или менее удачные, более удобные и менее :-) Согласитесь, что есть разница между простой правкой строки прямо в JS-коде и извлечением ключа в коде, поиском файла ресурса, затем поиском строки в нем по ключу, ее правкой и последующей перекомпиляцией и перезапуском проекта. Результат одинаков, а затраченное время разработчика и соответственно его продуктивность совершенно разные.

                                          Вы попробуйте наш подход, вам понравится :-)
                                            0
                                            Вы попробуйте наш подход, вам понравится
                                            У меня устойчивое недоверие к системам, чье поведение меняется в рантайме.

                                            Все остальное из вашего подхода применимо к rex, просто его надо рассматривать как репозиторий.

                                            (зато та часть вашего подхода, где строка выступает ключом к локализации, чувствительна к банальным ошибкам класса «опечатка» в тексте, когда после ее исправления вся уже созданная локализация пропадает)
                                              0
                                              Ну тут я спорить не буду. Мне resx и локализация крупных проектов знакомы не понаслышке, посмотрите комментарии выше. Важно что вам в вашем проекте удобнее resx, а нам наш подход. Серебряной пули нет :-)
                                                0
                                                Просто «ваш подход» надо делить на две части, и они друг с другом связаны не напрямую.

                                                Одна — это возможность локализации в рантайме (она, и только она обуславливает отказ от resx).

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

                                                  Строго говоря мы предлагаем не подход, а готовую, проверенную в боевых условиях библиотеку, добавляющую средства простой и интерактивной локализации в любой ASP.NET MVC проект буквально за 5-10 минут описанными нами выше средствами.

                                                  Мы не претендуем на то, что этот подход единственно верный и правильный. Сам много лет работал и с обычными виндовыми ресурсами в DLL и с resx и еще много с чем.

                                                  То что мы сделали нам нравится, оно заметно удобнее для разработчика и каких-либо существенных проблем в нашем довольно большом проекте мы пока не выявили. Собственно поэтому и решили выложить наши наработки в общий доступ. А уж использовать их или нет каждый решает сам. Но в любом случае спасибо за детальную и конструктивную дискуссию.
                                  0
                                  Для локализации представлений в web.config нужно указать, что в качестве базового класса для страниц будет использоваться LocalizedWebViewPage:

                                  А без базового класса совсем никак нельзя было сделать? Люди, у которых свои базовые классы, в этом месте плачут.
                                    0
                                    Никто не отменял наследование классов. Можно понаследоваться от LocalizedWebViewPage. В чем сложность?
                                      0
                                      В том, что существующий (а иногда — и third-party) код придется переписывать. Это плохая идея.

                                      Какая функциональность вам нужна, что вы не можете использовать (уже более-менее общепринятые) расширения HtmlHelper? Ну да, код будет на пять символов длиннее — зато вы не будете лезть в чужое дерево наследования.
                                        +1
                                        Тут согласен полностью. От кастомного базового класса для вьюх можно спокойно отказаться ценою 5 символов (Html.). Добавим в библиотеку и дополним описание.
                                    0
                                    Скажите, при использовании вашего провайдера для Entity Framework необходимо настраивать структуру бд или ещё что-то? Просто при той настройке, которая описана в статье(да и при той, которая в проекте sample в репозитории) проект сваливается с ошибкой
                                      0
                                      Структуру базы настраивать не нужно. Достаточно строки подключения. Какая у вас ошибка?
                                        0
                                        Для EntityType «LocalizedObject» не определены ключи. Определите ключ для этого EntityType.
                                        Например, код в коде действия:
                                        string a = "oeuo".Resource(this);
                                        
                                          0
                                          ASP.NET MVC 4
                                            0
                                            Напишите Stack Trace.
                                              0
                                              Ключ определен. Можете сами убедиться посмотрев исходник https://github.com/Knoema/Localization/blob/master/EFProvider/LocalizedObject.cs.

                                              Вас не затруднит stack trace привести?
                                                0
                                                StackTrace
                                                System.Data.Entity.ModelConfiguration.ModelValidationException не обработано пользовательским кодом
                                                HResult=-2146233088
                                                Message=Во время создания модели обнаружены ошибки проверки:

                                                \tSystem.Data.Entity.Edm.EdmEntityType:: Для EntityType «LocalizedObject» не определены ключи. Определите ключ для этого EntityType.
                                                \tSystem.Data.Entity.Edm.EdmEntitySet: EntityType: Набор EntitySet «Objects» основан на типе «LocalizedObject», в котором не определены ключи.

                                                Source=EntityFramework
                                                StackTrace:
                                                в System.Data.Entity.ModelConfiguration.Edm.EdmModelExtensions.ValidateAndSerializeCsdl(EdmModel model, XmlWriter writer)
                                                в System.Data.Entity.ModelConfiguration.Edm.EdmModelExtensions.ValidateCsdl(EdmModel model)
                                                в System.Data.Entity.DbModelBuilder.Build(DbProviderManifest providerManifest, DbProviderInfo providerInfo)
                                                в System.Data.Entity.DbModelBuilder.Build(DbConnection providerConnection)
                                                в System.Data.Entity.Internal.LazyInternalContext.CreateModel(LazyInternalContext internalContext)
                                                в System.Data.Entity.Internal.RetryLazy`2.GetValue(TInput input)
                                                в System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
                                                в System.Data.Entity.Internal.InternalContext.Initialize()
                                                в System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType)
                                                в System.Data.Entity.Internal.Linq.InternalSet`1.Initialize()
                                                в System.Data.Entity.Internal.Linq.InternalSet`1.get_InternalContext()
                                                в System.Data.Entity.Infrastructure.DbQuery`1.System.Linq.IQueryable.get_Provider()
                                                в System.Linq.Queryable.Where[TSource](IQueryable`1 source, Expression`1 predicate)
                                                в Knoema.Localization.EFProvider.LocalizationRepository.Knoema.Localization.ILocalizationRepository.GetAll(CultureInfo culture)
                                                в Knoema.Localization.LocalizationManager.GetAll(CultureInfo culture)
                                                в Knoema.Localization.LocalizationManager.GetLocalizedObject(CultureInfo culture, String hash)
                                                в Knoema.Localization.LocalizationManager.Translate(String scope, String text)
                                                в Knoema.Localization.StringExtensions.Resource(String value, Type type)
                                                в Knoema.Localization.StringExtensions.Resource(String value, Object obj)
                                                в KnoemaCustom.Controllers.HomeController.Index() в D:\Тестовые проекты\KnoemaCustom\KnoemaCustom\Controllers\HomeController.cs: строка 14
                                                в lambda_method(Closure, ControllerBase, Object[] )
                                                в System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
                                                в System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
                                                в System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
                                                в System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass42.b__41()
                                                в System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass8`1.b__7(IAsyncResult _)
                                                в System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
                                                в System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult)
                                                в System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.<>c__DisplayClass39.b__33()
                                                в System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass4f.b__49()
                                                InnerException:
                                                  0
                                                  Проблема была в том, что вместе с вашими пакетами загружалась текущая версия EntityFramework(5.0). А в ней, они, видимо, перенесли атрибут DatabaseGenerated(и не только его) в System.ComponentModel.DataAnnotations.Schema. В вашем же провайдере используется более ранняя версия EF.
                                                  После того, как я сделал необходимые изменения(заменил версию EF и в LocalizedObject добавил соответствующий namespace) и пересобрал решение — всё заработало.
                                                  Надеюсь, вы обратите на это внимание.
                                                    0
                                                    Мне кажется, в статью надо добавить описание настройки для корректной работы виджета: например, в web.config вставка handler:
                                                    <handlers>
                                                    <add name="Knoema.Localization" verb="*" path="_localization/*" type="Knoema.Localization.Web.LocalizationHandler"
                                                    allowPathInfo="true" />
                                                    </handlers>
                                                    
                                                      0
                                                      Если вы используете Nuget, то при установке эта настройка добавится в web.config автоматически.
                                                        0
                                                        Использование nuget в данном случае привело к уже описанной ошибке, поэтому в моем случае(и думаю не только в моем) эффективнее использовать подправленные исходники.
                                                          0
                                                          Поняла. Спасибо за конструктивные комментарии. В ближайшее время обновим в проекте EF до 5-ой версии и выложим на GitHub.
                                                            0
                                                            Спасибо.
                                              0
                                              хмм...

                                                0
                                                [UPD] все заработало. видимо был глюк гитхаба

                                              Only users with full accounts can post comments. Log in, please.