Turning around at @ FrancescoLorenzetti84, one of the ways I've done this in the past to simplify support is to wrap the database retrieval in the ResourceString class so you can do something like:
private static readonly ResourceString res = "The value";
and then refer to this in code. Behind the scene, the ResourceString class does the job. Here is an example of this:
namespace ResString { public interface IResourceResolver { string Resolve(string key, string defaultValue); } public class ResourceString { public ResourceString(string value) { this.defaultValue = value; GetOwner(); } public string Value { get { if (!resolved) Resolve(); return value; } } public override string ToString() { return Value; } public static implicit operator string(ResourceString rhs) { return rhs.Value; } public static implicit operator ResourceString(string rhs) { return new ResourceString(rhs); } protected virtual void Resolve() { if (Resolver != null) { if (key == null) key = GetKey(); value = Resolver.Resolve(key, defaultValue); } else { value = defaultValue; } resolved = true; } [MethodImpl(MethodImplOptions.NoInlining)] protected virtual void GetOwner() { StackTrace trace = new StackTrace(); StackFrame frame = null; int i = 1; while (i < trace.FrameCount && (owner == null || typeof(ResourceString).IsAssignableFrom(owner))) { frame = trace.GetFrame(i); MethodBase meth = frame.GetMethod(); owner = meth.DeclaringType; i++; } } protected virtual string GetKey() { string result = owner.FullName; FieldInfo field = owner.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).Where(f => typeof(ResourceString).IsAssignableFrom(f.FieldType) && f.GetValue(null) == this ).FirstOrDefault(); if (field != null) result += "." + field.Name; return result; } public static IResourceResolver Resolver { get; set; } private string defaultValue; private string value; private bool resolved; private string key; private Type owner; } }
And an example program:
namespace ResString { class Program { /// <summary> /// Description for the first resource. /// </summary> private static readonly ResourceString firstRes = "First"; /// <summary> /// Description for the second resource. /// </summary> private static readonly ResourceString secondRes = "Second"; /// <summary> /// Description for the format string. /// </summary> private static readonly ResourceString format = "{0} {1}"; static void Main(string[] args) { ResourceString.Resolver = new French(); Console.WriteLine(String.Format(format, firstRes, secondRes)); } private class French : IResourceResolver { public string Resolve(string key, string defaultValue) { switch (key) { case "ResString.Program.firstRes": return "Premier"; case "ResString.Program.secondRes": return "Deuxième"; case "ResString.Program.format": return "{1} {0}"; } return defaultValue; } } } }
If you run this, it will output: Deuxième Premier
Comment on the purpose of the Resolver, and you will receive: First second
Anywhere you would use a string in the user interface, use a declared ResourceString instead.
Changing the recognizer after the string values have been resolved will not change their value, since the values are retrieved only once. Of course, you will need to write a real resolver that pulls from the database.
Then you need a utility to run through the compiled classes and pull out ResourceString declarations and put the key and default values in the database or text file so that they can be translated. This should also go through the generated XML help files for each assembly and pull out the comment for ResourceString declarations so that the translator has some context to work with. Key announcements also provide context, as you can easily group assets by user interface class.
Add this to your build script, make sure it is updated regularly.
You can use the same approach with images, etc.