Skip to content

Instantly share code, notes, and snippets.

@GeorgDangl
Last active October 21, 2022 01:16
Show Gist options
  • Save GeorgDangl/70c01f40b2a2d49bbee58c3dffd7bad7 to your computer and use it in GitHub Desktop.
Save GeorgDangl/70c01f40b2a2d49bbee58c3dffd7bad7 to your computer and use it in GitHub Desktop.
{
"configurations": {
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}],
"deployUrl": "/dist/en/"
},
"production-de": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"fileReplacements": [{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}],
"i18nFile": "src/locale/messages.de.xlf",
"i18nFormat": "xlf",
"i18nLocale": "de",
"deployUrl": "/dist/de/"
}
}
}
namespace Project.SpaExtensions
{
public interface IUserLanguageService
{
string GetUserLocale();
}
}
namespace Project.SpaExtensions
{
public class LocalizedSpaStaticFilePathProvider
{
private readonly IUserLanguageService _userLanguageService;
private readonly string _distFolder;
public LocalizedSpaStaticFilePathProvider(IUserLanguageService userLanguageService,
string distFolder)
{
_userLanguageService = userLanguageService;
_distFolder = distFolder;
}
public string GetRequestPath(string subpath)
{
var userLocale = _userLanguageService.GetUserLocale();
var spaFilePath = "/" + _distFolder + "/" + userLocale + subpath;
return spaFilePath;
}
}
}
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Http;
namespace Project.SpaExtensions
{
public static class LocalizedSpaStaticFilesExtensions
{
public static void AddLocalizedSpaStaticFiles(this IServiceCollection services,
string[] availableLocales,
string spaRootPath)
{
services.AddTransient<IUserLanguageService>(serviceProvider =>
{
var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
return new UserLanguageService(httpContextAccessor, availableLocales);
});
services.AddTransient<LocalizedSpaStaticFilePathProvider>(serviceProvider =>
{
var userLanguageService = serviceProvider.GetRequiredService<IUserLanguageService>();
return new LocalizedSpaStaticFilePathProvider(userLanguageService, spaRootPath);
});
}
public static void UseLocalizedSpaStaticFiles(this IApplicationBuilder applicationBuilder, string defaultFile)
{
applicationBuilder.Use((context, next) =>
{
// In this part of the pipeline, the request path is altered to point to a localized SPA asset
var spaFilePathProvider = context.RequestServices.GetRequiredService<LocalizedSpaStaticFilePathProvider>();
context.Request.Path = spaFilePathProvider.GetRequestPath("/" + defaultFile.TrimStart('/'));
return next();
});
applicationBuilder.UseStaticFiles();
}
}
}
{
"scripts": {
"build:production": "ng build --prod --aot --output-path ../Project/wwwroot/dist/en",
"build:production-de": "ng build --prod --configuration=production-de --aot --output-path ../Project/wwwroot/dist/de"
}
}
namespace Project
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddLocalizedSpaStaticFiles(availableLocales: new[] { "de", "en" }, spaRootPath: "dist");
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseStaticFiles();
app.UseMvc();
// This serves localized SPA files from disk,
// e.g. from wwwroot/dist/en
app.UseLocalizedSpaStaticFiles("index.html");
}
}
}
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Http;
namespace Project.SpaExtensions
{
public class UserLanguageService : IUserLanguageService
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly List<string> _availableLanguages;
public UserLanguageService(IHttpContextAccessor httpContextAccessor,
IEnumerable<string> availableLanguages)
{
_httpContextAccessor = httpContextAccessor;
_availableLanguages = availableLanguages
.Select(l => l.ToLowerInvariant())
.ToList();
}
public string GetUserLocale()
{
var request = _httpContextAccessor.HttpContext?.Request;
if (request == null)
{
return _availableLanguages[0];
}
var cookieLocale = request.Cookies[".User.Locale"];
if (!string.IsNullOrWhiteSpace(cookieLocale))
{
return cookieLocale;
}
var userLocales = request.Headers["Accept-Language"].ToString();
var userAcceptLanguage = GetAcceptLanguageFromHeaderOrNull(userLocales);
if (!string.IsNullOrWhiteSpace(userAcceptLanguage))
{
return userAcceptLanguage;
}
return _availableLanguages[0];
}
public string GetAcceptLanguageFromHeaderOrNull(string headerValue)
{
if (headerValue == null)
{
return null;
}
try
{
var clientLanguages = (headerValue)
.Split(',')
.Select(StringWithQualityHeaderValue.Parse)
.OrderByDescending(language => language.Quality.GetValueOrDefault(1))
.Select(language => language.Value)
.Select(languageCode =>
{
if (languageCode.Contains("-"))
{
return languageCode.Split('-').First();
}
return languageCode;
})
.Select(languageCode => languageCode.ToLowerInvariant())
.Distinct()
.Where(languageCode => !string.IsNullOrWhiteSpace(languageCode) && languageCode.Trim() != "*");
return clientLanguages
.FirstOrDefault(clientLanguage => _availableLanguages.Contains(clientLanguage));
}
catch
{
return null;
}
}
}
}
@MussaratAziz
Copy link

Hi thanks for gist. very helpful i am trying to implement same logic in .NET 6 but unable to get it working. UserLanguageService never gets called am i missing something? Any suggestion will be helpful thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment