Локализация в Нэнси без бритвы viewengine

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

@Text.text.greeting

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

Или есть ли способ доступа к ресурсам с использованием модели?

Ответ 1

Хороший вопрос! Это то, что мне нужно было сделать самому.

Мне удалось решить эту проблему, основываясь на предположении, сделанном вам Карлом-Йоханом Шёгреном - т.е. мне удалось создать расширение для Super Simple View Engine (SSVE).


Фон

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

В SSVE вы увидите следующий конструктор (по состоянию на 2014/05/12), который позволит вам передать дополнительные "матчи":

    public SuperSimpleViewEngine(IEnumerable<ISuperSimpleViewEngineMatcher> matchers)
    {
        this.matchers = matchers ?? Enumerable.Empty<ISuperSimpleViewEngineMatcher>();

        this.processors = new List<Func<string, object, IViewEngineHost, string>>
        {
            PerformSingleSubstitutions,
            PerformContextSubstitutions,
            PerformEachSubstitutions,
            PerformConditionalSubstitutions,
            PerformPathSubstitutions,
            PerformAntiForgeryTokenSubstitutions,
            this.PerformPartialSubstitutions,
            this.PerformMasterPageSubstitutions,
        };
    }

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

Например, процессор/соединитель PerformSingleSubstitutions по умолчанию, который поставляется вместе с SSVE, используется для выполнения основной "@Model". замены. Может произойти следующий рабочий процесс процессора:

  • Текст '@Model.Name' сопоставляется в шаблоне представления.
  • Метод замещения запускается для замены параметра Model.
  • Некоторое отражение происходит против динамической модели, чтобы получить значение свойства Name.
  • Значение свойства "Имя" затем используется для замены строки "@Model.Name" в шаблоне представления.

Реализация

Итак, теперь, когда у нас есть основа, вот как вы можете создать свой собственный переводчик.:)

Сначала вам нужно создать реализацию ISuperSimpleViewEngineMatcher. Ниже приведен пример, который я создал для иллюстрации:

internal sealed class TranslateTokenViewEngineMatcher :
    ISuperSimpleViewEngineMatcher
{
    /// <summary>
    ///   Compiled Regex for translation substitutions.
    /// </summary>
    private static readonly Regex TranslationSubstitutionsRegEx;

    static TranslateTokenViewEngineMatcher()
    {
        // This regex will match strings like:
        // @Translate.Hello_World
        // @Translate.FooBarBaz;
        TranslationSubstitutionsRegEx =
            new Regex(
                @"@Translate\.(?<TranslationKey>[a-zA-Z0-9-_]+);?",
                RegexOptions.Compiled);
    }

    public string Invoke(string content, dynamic model, IViewEngineHost host)
    {
        return TranslationSubstitutionsRegEx.Replace(
            content,
            m =>
            {
                // A match was found!

                string translationResult;

                // Get the translation 'key'.
                var translationKey = m.Groups["TranslationKey"].Value;

                // Load the appropriate translation.  This could farm off to
                // a ResourceManager for example.  The below implementation
                // obviously isn't very useful and is just illustrative. :)
                if (translationKey == "Hello_World")
                {
                    translationResult = "Hello World!";
                }
                else 
                {
                    // We didn't find any translation key matches so we will
                    // use the key itself.
                    translationResult = translationKey;
                }

                return translationResult;
            });
    }
}

Хорошо, поэтому, когда вышеупомянутый матчи запускается против наших шаблонов, они найдут строки, начинающиеся с '@Translate.'. Текст сразу после "@Translate". считается нашим ключом перевода. Так, например, в из '@Translate.Hello_World', ключ перевода будет 'Hello_world'.

Когда происходит совпадение, метод replace запускается, чтобы найти и вернуть соответствующий перевод для ключа перевода. Мой текущий пример вернет перевод только для ключа "Hello_World" - вам, конечно же, придется заполнить свой собственный механизм, с помощью которого можно выполнить поиск перевода, возможно, прибегая к поддержке управления ресурсами .NET.

Матчи не будут автоматически подключены к SSVE, вам нужно будет использовать поддерживаемые IoC функции Nancy для регистрации вашего ответчика против этого параметра конструктора, который был выделен ранее.

Для этого вам необходимо переопределить метод ConfigureApplicationContainer в вашем загрузочном устройстве Nancy и добавить регистрацию, аналогичную следующей:

public class MyNancyBootstrapper : DefaultNancyBootstrapper
{
    protected override void ConfigureApplicationContainer(TinyIoCContainer container)
    {
        base.ConfigureApplicationContainer(container);

        // Register the custom/additional processors/matchers for our view
        // rendering within the SSVE
        container
            .Register<IEnumerable<ISuperSimpleViewEngineMatcher>>(
                (c, p) =>
                {
                    return new List<ISuperSimpleViewEngineMatcher>()
                    {
                        // This matcher provides support for @Translate. tokens
                        new TranslateTokenViewEngineMatcher()
                    };
                });
    }

    ...

Последний шаг - фактически добавить маркеры перевода к вашим представлениям:

<!-- index.sshtml -->
<html>
    <head>
        <title>Translator Test</title>
    </head>
    <body>
        <h1>@Translate.Hello_World;<h1>
    </body>
</html>

Как я уже сказал, это очень простой пример, который вы могли бы использовать в качестве основы для создания реализации в соответствии с вашими потребностями. Вы могли бы, например, расширить шаблон регулярного выражения, чтобы также учитывать целевую культуру, которую вы хотели бы перевести, или просто просто использовать текущую культуру потока, зарегистрированную в вашем приложении. У вас есть гибкость, чтобы сделать, как вам будет угодно.:)

Ответ 2

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

В моей модели у меня есть динамический объект Text с ресурсами на правильном языке.
(язык зависит от текущего пользователя и является int)

public dynamic Text { get; private set; }

В начале я создаю статический словарь для каждого языка.

private static Dictionary<int, dynamic> messages = null;

Я создал ResourceDictionary для заполнения динамического объекта:

public class ResourceDictionary : DynamicObject
{
    private Dictionary<string, string> dictionary;

    public ResourceDictionary()
    {
        dictionary = new Dictionary<string, string>();
    }

    public void Add(string key, string value)
    {
        dictionary.Add(key, value);
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        string data;
        if (!dictionary.TryGetValue(binder.Name, out data))
        {
            throw new KeyNotFoundException("Key not found!");
        }

        result = (string)data;

        return true;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        if (dictionary.ContainsKey(binder.Name))
        {
            dictionary[binder.Name] = (string)value;
        }
        else
        {
            dictionary.Add(binder.Name, (string)value);
        }

        return true;
    }
}