Skip to content

Instantly share code, notes, and snippets.

@ukcoderj
Last active September 19, 2018 10:45
Show Gist options
  • Save ukcoderj/d39565aa507438763f771361bfb36dcf to your computer and use it in GitHub Desktop.
Save ukcoderj/d39565aa507438763f771361bfb36dcf to your computer and use it in GitHub Desktop.
.NET Core Port Of afana.me's excellent i18n internationalisation (no magic strings in code - http://afana.me/archive/2013/11/01/aspnet-mvc-internationalization-store-strings-in-database-or-xml.aspx/)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace YourWebsite.i18n
{
/// <summary>
/// This is a .net core port of
/// http://afana.me/archive/2013/11/01/aspnet-mvc-internationalization-store-strings-in-database-or-xml.aspx/
/// The only major change is in i18n InitResources, where paths are now different. See startup.cs for initialization.
/// </summary>
public interface IResourceProvider
{
object GetResource(string name, string culture);
}
public abstract class BaseResourceProvider : IResourceProvider
{
// Cache list of resources
private static Dictionary<string, ResourceEntry> resources = null;
private static object lockResources = new object();
public BaseResourceProvider()
{
Cache = true; // By default, enable caching for performance
}
protected bool Cache { get; set; } // Cache resources ?
/// <summary>
/// Returns a single resource for a specific culture
/// </summary>
/// <param name="name">Resorce name (ie key)</param>
/// <param name="culture">Culture code</param>
/// <returns>Resource</returns>
public object GetResource(string name, string culture)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Resource name cannot be null or empty.");
if (string.IsNullOrWhiteSpace(culture))
throw new ArgumentException("Culture name cannot be null or empty.");
// normalize
culture = culture.ToLowerInvariant();
if (Cache && resources == null)
{
// Fetch all resources
lock (lockResources)
{
if (resources == null)
{
resources = ReadResources().ToDictionary(r => string.Format("{0}.{1}", r.Culture.ToLowerInvariant(), r.Name));
}
}
}
if (Cache)
{
return resources[string.Format("{0}.{1}", culture, name)].Value;
}
return ReadResource(name, culture).Value;
}
/// <summary>
/// Returns all resources for all cultures. (Needed for caching)
/// </summary>
/// <returns>A list of resources</returns>
internal abstract IList<ResourceEntry> ReadResources();
/// <summary>
/// Returns a single resource for a specific culture
/// </summary>
/// <param name="name">Resorce name (ie key)</param>
/// <param name="culture">Culture code</param>
/// <returns>Resource</returns>
protected abstract ResourceEntry ReadResource(string name, string culture);
}
}
...
<p>@YourWebsite.i18n.i18n.Site_Name</p>
...
using Microsoft.AspNetCore.Hosting;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
namespace YourWebsite.i18n
{
public partial class i18n
{
#region "Standard content - do not change"
private static IResourceProvider resourceProvider;
private static IList<ResourceEntry> _resources;
public static void InitResources(IHostingEnvironment hostingEnvironment)
{
var x = hostingEnvironment.WebRootPath;
resourceProvider =
new XmlResourceProvider(Path.Combine(hostingEnvironment.WebRootPath, @"App_Resources\Resources.xml"));
_resources = ((XmlResourceProvider)resourceProvider).ReadResources();
}
internal static void ReloadResources(IHostingEnvironment hostingEnvironment)
{
InitResources(hostingEnvironment);
}
public static void TestLocaleSpecificRequest()
{
var x1 = i18n.GetLocalisedStringValue(() => i18n.Site_Name, "en-GB");
var x2 = i18n.GetLocalisedStringValue(() => i18n.Site_Name, "fr-FR");
}
public static string GetLocalisedStringValue<T>(Expression<Func<T>> propertyLambda, string cultureName)
{
var me = propertyLambda.Body as MemberExpression;
if (me == null)
{
throw new ArgumentException("You must pass a lambda of the form: '() => Class.Property' or '() => object.Property'");
}
var propertyName = me.Member.Name;
if (String.IsNullOrWhiteSpace(cultureName))
{
cultureName = "en-GB";
}
// Try to get a value in the locale requested.
var returnValue = _resources.FirstOrDefault(i => i.Name == propertyName && i.Culture == cultureName)?.Value;
// No return value for the culture, fall back to english
returnValue = returnValue ?? _resources.FirstOrDefault(i => i.Name == propertyName && i.Culture == "en-GB")?.Value;
// Still no value, show a really obvious error.
returnValue = returnValue ?? $"(RESOURCE NOT FOUND FOR CULTURE {CultureInfo.CurrentUICulture.Name})";
return returnValue;
}
/// <summary>
/// Get the value of a resource based on the current culture.
/// </summary>
/// <param name="propertyName">The calling property.</param>
/// <returns>the value of a resource based on the current culture</returns>
private static string GetStringValue([CallerMemberName] string propertyName = null)
{
// Try to get a value in the locale requested.
var returnValue = _resources.FirstOrDefault(i => i.Name == propertyName && i.Culture == CultureInfo.CurrentUICulture.Name)?.Value;
// No return value for the culture, fall back to english
returnValue = returnValue ?? _resources.FirstOrDefault(i => i.Name == propertyName && i.Culture == "en-GB")?.Value;
// Still no value, show a really obvious error.
returnValue = returnValue ?? $"(RESOURCE NOT FOUND FOR CULTURE {CultureInfo.CurrentUICulture.Name})";
return returnValue;
}
internal static List<string> GetAllCultures()
{
return _resources.Select(_ => _.Culture).Distinct().OrderBy(_ => _).ToList();
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace YourWebsite.i18n
{
public partial class i18n
{
public static string Site_Name { get { return GetStringValue(); } }
}
}
1. This assumes you have placed the resources.xml in {project}/wwwroot/App_Resources/resources.xml
2. This also has a method to choose your culture at runtime e.g. Resources.i18n.GetLocalisedStringValue(() => i18n.Site_Name, "fr-FR");
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace YourWebsite.i18n
{
public class ResourceEntry
{
public string Name { get; set; }
public string Value { get; set; }
public string Culture { get; set; }
public string Type { get; set; }
public ResourceEntry()
{
Type = "string";
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<!-- ENGLISH (this could be en-GB) -->
<resource culture="en-GB" type="string" name="Site_Name" value="Site Name (English)"></resource>
<!-- FRENCH -->
<resource culture="fr-FR" type="string" name="Site_Name" value="Site Name (French)"></resource>
</resources>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using RegAndVote.Data;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.Extensions.Logging;
namespace YouWebsite
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//...
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//...
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
// *** i18n Initialisation!!! ***
i18n.i18n.InitResources(env);
}
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace YourWebsite.i18n
{
/// <summary>
/// .net core port of
/// http://afana.me/archive/2013/11/01/aspnet-mvc-internationalization-store-strings-in-database-or-xml.aspx/
/// </summary>
public class XmlResourceProvider : BaseResourceProvider
{
// File path
private static string filePath = null;
public XmlResourceProvider() { }
public XmlResourceProvider(string filePath)
{
XmlResourceProvider.filePath = filePath;
if (!File.Exists(filePath)) throw new FileNotFoundException(string.Format("XML Resource file {0} was not found", filePath));
}
internal override IList<ResourceEntry> ReadResources()
{
// Parse the XML file
return XDocument.Parse(File.ReadAllText(filePath))
.Element("resources")
.Elements("resource")
.Select(e => new ResourceEntry
{
Name = e.Attribute("name").Value,
Value = e.Attribute("value").Value,
Culture = e.Attribute("culture").Value
}).ToList();
}
protected override ResourceEntry ReadResource(string name, string culture)
{
// Parse the XML file
return XDocument.Parse(File.ReadAllText(filePath))
.Element("resources")
.Elements("resource")
.Where(e => e.Attribute("name").Value == name && e.Attribute("culture").Value == culture)
.Select(e => new ResourceEntry
{
Name = e.Attribute("name").Value,
Value = e.Attribute("value").Value,
Culture = e.Attribute("culture").Value
}).FirstOrDefault();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment