For the moment I\'m using razor as my view engine in Nancy.
I can access my resource file like this in razor:
@Text.text.greeting
But I wo
I now made my own solution because I couldn't use resource files.
In my model I have a dynamic Text object with the resources in the correct language.
(the language depends on the current user and is a int)
public dynamic Text { get; private set; }
At the start I build a static dictionary per language.
private static Dictionary<int, dynamic> messages = null;
I created a ResourceDictionary to fill the dynamic object:
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;
}
}
Good question! This is something that I have needed to do myself.
I managed to solve this problem using the basis of the suggestion made to you by @Karl-Johan Sjögren - i.e. I was able to create an extension to the Super Simple View Engine (SSVE).
The Background
The SSVE has been designed in such a way that you can inject additional 'matchers' that allow you to do some processing on your view templates as they are getting rendered for the output of a request.
You will notice the following constructor (as of 2014/05/12) within the SSVE, that allows you to pass in additional 'matchers':
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,
};
}
The basic way that most of the template substitution works in the SSVE is by doing very simple regular expression matches against the view templates. If a regular expression is matched, then a substitution method is invoked within which the appropriate substitution occurs.
For example, the default PerformSingleSubstitutions processor/matcher that comes with the SSVE is used to do your basic '@Model.' substitutions. The following processor workflow could occur:
The Implementation
Ok, so now that we have the foundation, here is how you can create your very own Translation matcher. :)
First you will need to create an implementation of ISuperSimpleViewEngineMatcher. Below is a really basic example I have created for the purpose of illustration:
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;
});
}
}
Okay, so when the above matcher is run against our view templates they will to find strings starting with '@Translate.'. The text just after the '@Translate.' is considered to be our translation key. So in the e.g. of '@Translate.Hello_World', the translation key would be 'Hello_world'.
When a match occurs the replace method is fired to find and return the appropriate translation for the translation key. My current example will only return a translation for the key of 'Hello_World' - you would of course have to fill in your own mechanism with which to do the translation lookups, perhaps farming off to the default resource management support of .net?
The matcher won't get automatically hooked up into the SSVE, you will have to use the IoC supported features of Nancy to register your matcher against that constructor parameter I highlighted earlier.
To do so you will need to override the ConfigureApplicationContainer method within your Nancy bootstrapper and add a registration similar to the one below:
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()
};
});
}
...
The final step is to actually add your translation tokens to your views:
<!-- index.sshtml -->
<html>
<head>
<title>Translator Test</title>
</head>
<body>
<h1>@Translate.Hello_World;<h1>
</body>
</html>
As I said, this is a very basic example which you could use as the basis to create an implementation to suit your needs. You could for example extend the regular expression matcher to also take into account the target culture that you would like to translate into, or just simply use the current thread culture registered within your application. You have the flexibility to do as you please. :)