How to do extreme branding/internationalization in .NET

旧城冷巷雨未停 提交于 2019-12-05 05:59:37

I wouldn't recommend using .resx files for such a huge project. When a website is translated in many different languages, usually a lot of people are involved in the copy management, translation etc. These people will not be able to edit the .resx files since they are technically embedded in the application code. This means that your developers will have to constantly update the resources every time there are changes... a real nightmare for everybody.

I recently build a database-driven system for the SumoSoft.Cms. All the strings can be managed through the Admin panel, while in the code you just use:

@CmsMethods.StringContent("ContentSection_Name", "Fallback_Value")

This Helper queries the Database looking for an entity of Type "ContentSection" which is structured more or less like this:

public class ContentSection
{
    public string Name { get; set; }

    public ICollection<ContentSectionLocalizedString> LocalizedStrings { get; set; }
}

Each LocalizedString contains a reference to a specific Country and a property "Content", so all the Helper does is to choose the one that matches the Current Culture of the Thread.

Expanding on @FrancescoLorenzetti84 answer, one way I've done it in the past to make it easier to maintain is to wrap the database retrieval in a ResourceString class so that you can do something like:

private static readonly ResourceString res = "The value";

and then refer to that in the code. Behind the scene, the ResourceString class does the work. Here is an example of that:

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 that, it will output: Deuxième Premier

Comment out the Resolver assignment and you will get: First Second

Any where you would use a string in the UI, use a declared ResourceString instead.

Changing the resolver after the string values have been resolved will not alter their value as the values are retrieved only once. You will of course need to write a real resolver that pulls from a database.

What you will then need is a utility program to run through the compiled classes and pull out the ResourceString declarations and put the key and default values into a database or text file so they can be translated. This should also go through the generated help XML files for each assembly and pull the comment for the ResourceString declarations so the translator has some context to work with. The key declarations will also provide context as you can easily group resources by UI class.

Add this to a build script the make sure it is updated regularly.

You can use the same approach with images and the like.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!