Closed martinrulf closed 2 years ago
Welcome @martinrulf :)
Thank you for your suggestion, I will check it and see the possibilities to integrate with XLocalizer.
Best, Ziya
Just did some tests and found how to use Smart.Format
with XLocalizer
:
1- Decorate the placeholders with double curly brackets {{Sample}}
to avoid string format exception
2- Localize the text without providing parameters
3- Parse the locaized text to Smart.Format
with relevant data
4- Make sure the localized text has the correct conditional values in English!
public class HomeController
private readonly IStringLocalizer _loc;
public HomeController(IStringLocalizer loc)
{
_loc = loc;
}
public IActionResult Index()
{
var data = new { Library = "XLocalizer" };
// use double curly brackets to avoid string format exception.
// don't parse parameters here.
// make sure that you have {{Library}} in the localized text
// e.g. Turkish: "{{Library}} ile lokalleştirildi."
var localizedText = _loc["Localized with {{Library}}"];
// parse the localized text to Smart.Format
var str = Smart.Format(localizedText, data)
// output str: "XLocalizer ile lokalleştirildi."
}
}
Hello @LazZiya,
A good trick, thank you. :) in the meantime I resolved this a bit differently.
Basicaly I copied and modified your code for DbStringLocalizer (MyDbStringLocalizer)
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SmartFormat;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using XLocalizer.Common;
using XLocalizer.DB.Models;
using XLocalizer.Translate;
namespace XLocalizer.DB
{
/// <summary>
/// DbStringLocalizer
/// </summary>
/// <typeparam name="TResource"></typeparam>
public class MyDbStringLocalizer<TResource> : IStringLocalizer, IStringLocalizer<TResource>
where TResource : class, IXDbResource, new()
{
private readonly IDbResourceProvider _provider;
private readonly ITranslator _translator;
private readonly ExpressMemoryCache _cache;
private readonly XLocalizerOptions _options;
private readonly ILogger _logger;
private string _transCulture;
/// <summary>
/// Initialize a new instance of DbStringLocalizer
/// </summary>
/// <param name="options"></param>
/// <param name="provider"></param>
/// <param name="translatorFactory"></param>
/// <param name="cache"></param>
/// <param name="loggerFactory"></param>
/// <param name="localizationOptions"></param>
public MyDbStringLocalizer(IDbResourceProvider provider,
ITranslatorFactory translatorFactory,
ExpressMemoryCache cache,
IOptions<XLocalizerOptions> options,
IOptions<RequestLocalizationOptions> localizationOptions,
ILoggerFactory loggerFactory)
{
_options = options.Value;
_provider = provider;
_translator = translatorFactory.Create();
_cache = cache;
_logger = loggerFactory.CreateLogger<DbStringLocalizer<TResource>>();
_transCulture = options.Value.TranslateFromCulture ?? localizationOptions.Value.DefaultRequestCulture.Culture.Name;
}
/// <summary>
/// Get localized string
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public LocalizedString this[string name] => GetLocalizedString(name);
/// <summary>
/// Get localized string with arguments
/// </summary>
/// <param name="name"></param>
/// <param name="arguments"></param>
/// <returns></returns>
public LocalizedString this[string name, params object[] arguments] => GetLocalizedString(name, arguments);
/// <summary>
/// NOT IMPLEMENTED
/// </summary>
/// <param name="includeParentCultures"></param>
/// <returns></returns>
public IEnumerable<LocalizedString> GetAllStrings(bool includeParentCultures)
{
throw new NotImplementedException();
}
/// <summary>
/// NOT IMPLEMENTED! use <see cref="CultureSwitcher"/> instead.
/// </summary>
/// <param name="culture"></param>
/// <returns></returns>
public IStringLocalizer WithCulture(CultureInfo culture)
{
throw new NotImplementedException();
}
private LocalizedString GetLocalizedString(string name, params object[] arguments)
{
var targetCulture = CultureInfo.CurrentCulture.Name;
var targetEqualSource = _transCulture.Equals(targetCulture, StringComparison.OrdinalIgnoreCase);
// Option 0: Skip localization if:
// LocalizeDefaultCulture == false and currentCulture == _transCulture
if (!_options.LocalizeDefaultCulture && targetEqualSource)
{
return new LocalizedString(name, Smart.Format(name, arguments), resourceNotFound: true, searchedLocation: string.Empty);
}
// Option 1: Look in the cache
bool availableInCache = _cache.TryGetValue<TResource>(name, out string value);
if (availableInCache)
{
return new LocalizedString(name, Smart.Format(value, arguments), false, string.Empty);
}
// Option 2: Look in db source
bool availableInSource = _provider.TryGetValue<TResource>(name, out value);
if (availableInSource)
{
// Add to cache
_cache.Set<TResource>(name, value);
return new LocalizedString(name, Smart.Format(value, arguments), false, string.Empty);
}
// Option 3: Try online translation service
// and don't do online translation if target == source culture,
// because online tranlsation services requires two different cultures.
var availableInTranslate = false;
if (_options.AutoTranslate && !targetEqualSource)
{
var toLocalize = name?.Split("_")?.Last();
availableInTranslate = _translator.TryTranslate(_transCulture, CultureInfo.CurrentCulture.Name, toLocalize, out value);
if (availableInTranslate)
{
// Add to cache
_cache.Set<TResource>(name, value);
}
}
// Save to db when AutoAdd is anebled and:
// A:translation success or, B: AutoTranslate is off or, C: Target and source cultures are same
// option C: useful when we use code keys to localize defatul culture as well
if (_options.AutoAddKeys && (availableInTranslate || !_options.AutoTranslate || targetEqualSource))
{
if ((value == name && value != null) || value == null)
{
value = name?.Split("_")?.Last();
}
var res = new TResource
{
Key = name,
Value = value,
Comment = "Created by XLocalizer",
CultureID = CultureInfo.CurrentCulture.Name,
IsActive = false
};
bool savedToResource = _provider.TrySetValue<TResource>(res);
_logger.LogInformation($"Save resource to db, status: '{savedToResource}', key: '{name}', value: '{value ?? name}'");
}
return new LocalizedString(name, Smart.Format(value, arguments), !availableInTranslate, typeof(TResource).FullName);
}
}
}
Then I registered this in DI setup like this
This approach is handy, because I was able to further modify the logic there.
For example I use some prefixes for keys when I construct them.
Something like AppName_Controller_View_Key
name?.Split("_")?.Last();
But when the online translation is called (or the raw key is used as a default translation) I remove all the prefixes. i.e. only Key is stored/translated.
Anyway, thank you very much for your help, it is much appreciated.
Hello again @LazZiya :)
I would like to ask you, if you could use rather
Smart.Format https://github.com/axuno/SmartFormat
instead of string.Format
in cases when you return the Microsoft.Extensions.Localization.LocalizedString
for example here
https://github.com/LazZiya/XLocalizer.DB/blob/db5b6736d3b9d580199bea6ec4c0d22abe45c33d/XLocalizer.DB/DbStringLocalizer.cs#L103
SmartFormat is a string composition library written in C# which is basically compatible with string.Format, it can replace it completely without breaking changes, so you don't have to do any further changes in your code. :)
In return you get support of named parameters in localization keys, that is something that I would like to use. But currently I can't do that even with a workaround.
Could you do that?
BR, Martin