Skip to content

Commit acaa318

Browse files
committed
Feature : Translation Abstraction and Parrot Added
1 parent d94ff30 commit acaa318

File tree

17 files changed

+533
-0
lines changed

17 files changed

+533
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Extensions.Translations.Parrot.DataModel;
2+
3+
public class LocalizationRecord
4+
{
5+
public long Id { get; set; }
6+
public Guid BusinessId { get; set; }
7+
public string Key { get; set; } = string.Empty;
8+
public string Value { get; set; } = string.Empty;
9+
public string Culture { get; set; } = string.Empty;
10+
public bool CorrespondingValueNotFound => string.IsNullOrEmpty(Value);
11+
public override string ToString() => Value.ToString();
12+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
using Dapper;
2+
using Extensions.Translations.Parrot.DataModel;
3+
using Extensions.Translations.Parrot.Options;
4+
using Microsoft.Data.SqlClient;
5+
using Microsoft.Extensions.Logging;
6+
using System.Data;
7+
8+
namespace Extensions.Translations.Parrot.Database;
9+
10+
public class ParrotSqlRepository
11+
{
12+
private readonly IDbConnection _dbConnection;
13+
private List<LocalizationRecord> _localizationRecords;
14+
15+
static readonly object _locker = new();
16+
17+
private readonly ParrotTranslatorOptions _configuration;
18+
private readonly ILogger _logger;
19+
private readonly string _selectCommand = "Select * from [{0}].[{1}]";
20+
private readonly string _insertCommand = "INSERT INTO [{0}].[{1}]([Key],[Value],[Culture]) VALUES (@Key,@Value,@Culture) select SCOPE_IDENTITY()";
21+
22+
public ParrotSqlRepository(ParrotTranslatorOptions configuration,
23+
ILogger logger)
24+
{
25+
_configuration = configuration;
26+
_logger = logger;
27+
_dbConnection = new SqlConnection(configuration.ConnectionString);
28+
29+
if (_configuration.AutoCreateSqlTable)
30+
CreateTableIfNeeded();
31+
32+
_selectCommand = string.Format(_selectCommand, configuration.SchemaName, configuration.TableName);
33+
34+
_insertCommand = string.Format(_insertCommand, configuration.SchemaName, configuration.TableName);
35+
36+
LoadLocalizationRecords(null);
37+
38+
SeedData();
39+
40+
LoadLocalizationRecords(null);
41+
42+
_ = new Timer(LoadLocalizationRecords,
43+
null,
44+
TimeSpan.FromMinutes(configuration.ReloadDataIntervalInMinuts),
45+
TimeSpan.FromMinutes(configuration.ReloadDataIntervalInMinuts));
46+
47+
}
48+
49+
private void CreateTableIfNeeded()
50+
{
51+
try
52+
{
53+
string table = _configuration.TableName;
54+
string schema = _configuration.SchemaName;
55+
56+
_logger.LogInformation("Parrot Translator try to create table in database with connection {ConnectionString}. Schema name is {Schema}. Table name is {TableName}", _configuration.ConnectionString, schema, table);
57+
58+
59+
string createTable = $"IF (NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE " +
60+
$"TABLE_SCHEMA = '{schema}' AND TABLE_NAME = '{table}' )) Begin " +
61+
$"CREATE TABLE [{schema}].[{table}]( " +
62+
$"Id bigint Primary Key Identity(1,1)," +
63+
$"[BusinessId] [UNIQUEIDENTIFIER] NOT NULL UNIQUE default(newId())," +
64+
$"[Key] [nvarchar](255) NOT NULL," +
65+
$"[Value] [nvarchar](500) NOT NULL," +
66+
$"[Culture] [nvarchar](5) NULL)" +
67+
$" End";
68+
_dbConnection.Execute(createTable);
69+
}
70+
catch (Exception ex)
71+
{
72+
_logger.LogError(ex, "Create table for Parrot Translator Failed");
73+
throw;
74+
}
75+
76+
}
77+
78+
private void LoadLocalizationRecords(object? state)
79+
{
80+
lock (_locker)
81+
{
82+
_logger.LogInformation("Parrot Translator load localization recored at {DateTime}", DateTime.Now);
83+
84+
_localizationRecords = _dbConnection.Query<LocalizationRecord>(_selectCommand, commandType: CommandType.Text).ToList();
85+
86+
_logger.LogInformation("Parrot Translator loaded localization recored at {DateTime}. Total record count is {RecordCount}", DateTime.Now, _localizationRecords.Count);
87+
}
88+
}
89+
90+
private void SeedData()
91+
{
92+
string values = string.Empty;
93+
94+
var newItemsInDefaultTranslations = _configuration.DefaultTranslations.
95+
Where(c => !_localizationRecords.Any(d => d.Key.Equals(c.Key) && d.Culture.Equals(c.Culture)))
96+
.Select(c => $"(N'{c.Key}',N'{c.Value}',N'{c.Culture}')").ToList();
97+
98+
var count = newItemsInDefaultTranslations.Count;
99+
if (count > 0)
100+
{
101+
string _command = $"INSERT INTO [{_configuration.SchemaName}].[{_configuration.TableName}]([Key],[Value],[Culture]) VALUES {string.Join(",", newItemsInDefaultTranslations)}";
102+
103+
using IDbConnection db = new SqlConnection(_configuration.ConnectionString);
104+
105+
db.Execute(_command, commandType: CommandType.Text);
106+
107+
_logger.LogInformation("Parrot Translator Add {Count} items to its dictionary from default translations at {DateTime}", count, DateTime.Now);
108+
109+
}
110+
}
111+
112+
public string Get(string key, string culture)
113+
{
114+
try
115+
{
116+
var record = _localizationRecords.FirstOrDefault(c => c.Key == key && c.Culture == culture);
117+
if (record == null)
118+
{
119+
_logger.LogInformation("The key was not found and was registered with the default value in Parrot Translator. Key is {Key} and culture is {Culture}", key, culture);
120+
record = new LocalizationRecord
121+
{
122+
Key = key,
123+
Culture = culture,
124+
Value = key
125+
};
126+
127+
var parameters = new DynamicParameters();
128+
parameters.Add("@Key", key);
129+
parameters.Add("@Culture", culture);
130+
parameters.Add("@Value", key);
131+
132+
record.Id = _dbConnection.Query<int>(_insertCommand, param: parameters, commandType: CommandType.Text).FirstOrDefault();
133+
_localizationRecords.Add(record);
134+
}
135+
return record.Value;
136+
}
137+
catch (Exception ex)
138+
{
139+
_logger.LogError(ex, "Get Key value from Sql Server for Parrot Translator Failed");
140+
throw;
141+
}
142+
143+
}
144+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<Authors>Kamran Tajerbashi</Authors>
8+
<ImplicitUsings>enable</ImplicitUsings>
9+
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
10+
<RepositoryUrl>https://github.com/KTajerbashi/Extensions.git</RepositoryUrl>
11+
<PackageIcon>icon.png</PackageIcon>
12+
<Title>TranslationsParrot</Title>
13+
<Version>2.0.0</Version>
14+
<ApplicationIcon>icon.ico</ApplicationIcon>
15+
</PropertyGroup>
16+
17+
<ItemGroup>
18+
<Content Remove="C:\Users\Tajer\.nuget\packages\extensions.translations.abstractions\2.0.0\contentFiles\any\net9.0\icon.ico" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<Content Include="icon.ico" />
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<None Include="icon.png" Pack="true" PackagePath="" />
27+
</ItemGroup>
28+
29+
<ItemGroup>
30+
<PackageReference Include="Dapper" Version="2.1.35" />
31+
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
32+
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
33+
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" />
34+
<PackageReference Include="Extensions.Translations.Abstractions" Version="2.0.0" />
35+
</ItemGroup>
36+
37+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Extensions.Translations.Abstractions;
2+
using Extensions.Translations.Parrot.Options;
3+
using Extensions.Translations.Parrot.Services;
4+
using Microsoft.Extensions.Configuration;
5+
using Microsoft.Extensions.DependencyInjection;
6+
7+
namespace Extensions.Translations.Parrot.Extensions.DependencyInjection;
8+
9+
public static class ParrotTranslatorServiceCollectionExtensions
10+
{
11+
public static IServiceCollection AddParrotTranslator(this IServiceCollection services, IConfiguration configuration)
12+
{
13+
services.AddSingleton<ITranslator, ParrotTranslator>();
14+
services.Configure<ParrotTranslatorOptions>(configuration);
15+
return services;
16+
}
17+
18+
public static IServiceCollection AddParrotTranslator(this IServiceCollection services, IConfiguration configuration, string sectionName)
19+
{
20+
services.AddParrotTranslator(configuration.GetSection(sectionName));
21+
return services;
22+
}
23+
24+
public static IServiceCollection AddParrotTranslator(this IServiceCollection services, Action<ParrotTranslatorOptions> setupAction)
25+
{
26+
services.AddSingleton<ITranslator, ParrotTranslator>();
27+
services.Configure(setupAction);
28+
return services;
29+
}
30+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Extensions.Translations.Parrot.Database;
2+
3+
public class DefaultTranslationOption
4+
{
5+
public string Key { get; set; } = nameof(Key);
6+
public string Value { get; set; } = nameof(Value);
7+
public string Culture { get; set; } = nameof(Culture);
8+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using Extensions.Translations.Parrot.Database;
2+
3+
namespace Extensions.Translations.Parrot.Options;
4+
5+
public class ParrotTranslatorOptions
6+
{
7+
public string ConnectionString { get; set; } = string.Empty;
8+
public bool AutoCreateSqlTable { get; set; } = true;
9+
public string TableName { get; set; } = "ParrotTranslations";
10+
public string SchemaName { get; set; } = "dbo";
11+
public int ReloadDataIntervalInMinuts { get; set; }
12+
public DefaultTranslationOption[] DefaultTranslations { get; set; } = Array.Empty<DefaultTranslationOption>();
13+
14+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using Extensions.Translations.Abstractions;
2+
using Extensions.Translations.Parrot.Database;
3+
using Extensions.Translations.Parrot.Options;
4+
using Microsoft.Extensions.Logging;
5+
using Microsoft.Extensions.Options;
6+
using System.Globalization;
7+
8+
namespace Extensions.Translations.Parrot.Services;
9+
10+
public class ParrotTranslator : ITranslator, IDisposable
11+
{
12+
private readonly string _currentCulture;
13+
private readonly ParrotSqlRepository _localizer;
14+
private readonly ILogger<ParrotTranslator> _logger;
15+
16+
public ParrotTranslator(IOptions<ParrotTranslatorOptions> configuration, ILogger<ParrotTranslator> logger)
17+
{
18+
_currentCulture = CultureInfo.CurrentCulture.ToString();
19+
_logger = logger;
20+
if (string.IsNullOrWhiteSpace(_currentCulture))
21+
{
22+
_currentCulture = "en-US";
23+
_logger.LogInformation("Parrot Translator current culture is null and set to en-US");
24+
}
25+
_localizer = new ParrotSqlRepository(configuration.Value, logger);
26+
_logger.LogInformation("Parrot Translator Start working with culture {Culture}", _currentCulture);
27+
}
28+
29+
30+
public string this[string name] { get => GetString(name); set => throw new NotImplementedException(); }
31+
public string this[CultureInfo culture, string name] { get => GetString(culture, name); set => throw new NotImplementedException(); }
32+
33+
34+
public string this[string name, params string[] arguments] { get => GetString(name, arguments); set => throw new NotImplementedException(); }
35+
public string this[CultureInfo culture, string name, params string[] arguments] { get => GetString(culture, name, arguments); set => throw new NotImplementedException(); }
36+
37+
38+
public string this[char separator, params string[] names] { get => GetConcateString(separator, names); set => throw new NotImplementedException(); }
39+
public string this[CultureInfo culture, char separator, params string[] names] { get => GetConcateString(culture, separator, names); set => throw new NotImplementedException(); }
40+
41+
42+
public string GetString(string name)
43+
{
44+
_logger.LogTrace("Parrot Translator GetString with name {name}", name);
45+
return _localizer.Get(name, _currentCulture);
46+
47+
}
48+
public string GetString(CultureInfo culture, string name)
49+
{
50+
_logger.LogTrace("Parrot Translator GetString with culture {culture} name {name}", culture, name);
51+
if (culture is null)
52+
return _localizer.Get(name, _currentCulture);
53+
else return _localizer.Get(name, culture.ToString());
54+
}
55+
56+
57+
public string GetString(string pattern, params string[] arguments)
58+
{
59+
_logger.LogTrace("Parrot Translator GetString with pattern {pattern} and arguments {arguments}", pattern, arguments);
60+
61+
for (int i = 0; i < arguments.Length; i++)
62+
{
63+
arguments[i] = GetString(arguments[i]);
64+
}
65+
66+
pattern = GetString(pattern);
67+
68+
for (int i = 0; i < arguments.Length; i++)
69+
{
70+
string placeHolder = $"{{{i}}}";
71+
pattern = pattern.Replace(placeHolder, arguments[i]);
72+
}
73+
74+
return pattern;
75+
}
76+
77+
public string GetString(CultureInfo culture, string pattern, params string[] arguments)
78+
{
79+
_logger.LogTrace("Parrot Translator GetString with culture {culture} and pattern {pattern} and arguments {arguments}", culture, pattern, arguments);
80+
81+
for (int i = 0; i < arguments.Length; i++)
82+
{
83+
arguments[i] = GetString(culture, arguments[i]);
84+
}
85+
86+
pattern = GetString(culture, pattern);
87+
88+
for (int i = 0; i < arguments.Length; i++)
89+
{
90+
string placeHolder = $"{{{i}}}";
91+
pattern = pattern.Replace(placeHolder, arguments[i]);
92+
}
93+
94+
return pattern;
95+
}
96+
97+
public string GetConcateString(char separator = ' ', params string[] names)
98+
{
99+
_logger.LogTrace("Parrot Translator GetConcateString with separator {separator} and names {names}", separator, names);
100+
101+
for (int i = 0; i < names.Length; i++)
102+
{
103+
names[i] = GetString(names[i]);
104+
}
105+
106+
return string.Join(separator, names);
107+
}
108+
109+
public string GetConcateString(CultureInfo culture, char separator = ' ', params string[] names)
110+
{
111+
_logger.LogTrace("Parrot Translator GetConcateString with culture {culture} and separator {separator} and names {names}", culture, separator, names);
112+
113+
for (int i = 0; i < names.Length; i++)
114+
{
115+
names[i] = GetString(culture, names[i]);
116+
}
117+
118+
return string.Join(separator, names);
119+
}
120+
121+
public void Dispose()
122+
{
123+
_logger.LogInformation("Parrot Translator Stop working with culture {Culture}", _currentCulture);
124+
}
125+
}
Binary file not shown.

0 commit comments

Comments
 (0)