Skip to content

Commit e09bcaf

Browse files
authored
Merge pull request #486 from Inxton/add-localization-override-from-user-application
Add app-level override for localization resources
2 parents 560fff4 + 18b3cb0 commit e09bcaf

6 files changed

Lines changed: 158 additions & 12 deletions

File tree

src/AXSharp.connectors/src/AXSharp.Connector/Localizations/Translator.cs

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Globalization;
44
using System.Linq;
@@ -17,9 +17,20 @@ namespace AXSharp.Connector.Localizations
1717
/// </summary>
1818
public class Translator
1919
{
20-
//private ResxLocalizations LocalizationResources { get; set; }
21-
22-
private ResourceManager _resourceManager;
20+
private IEnumerable<ResourceManager> ResourceManagers
21+
{
22+
get
23+
{
24+
// IMPORTANT: order matters. Application should be searched first to allow overrides.
25+
// Do not cache: resources can be configured at runtime (e.g., SetPrimaryTranslatorResource).
26+
if (_applicationResourceManager != null) yield return _applicationResourceManager;
27+
if (_libraryResourceManager != null) yield return _libraryResourceManager;
28+
}
29+
}
30+
31+
private static ResourceManager _applicationResourceManager;
32+
33+
private ResourceManager _libraryResourceManager;
2334

2435
private CultureInfo Culture = CultureInfo.InvariantCulture;
2536

@@ -45,7 +56,7 @@ public void SetLocalizationResource(Type resourceType)
4556
{
4657
if (resourceType != null)
4758
{
48-
_resourceManager = new ResourceManager(resourceType)
59+
_libraryResourceManager = new ResourceManager(resourceType)
4960
{
5061
IgnoreCase = true
5162
};
@@ -115,20 +126,28 @@ private string LocalizeInParents(string token, ITwinElement rootObj, CultureInfo
115126

116127
public string Localize(string str, ITwinElement twinElement, CultureInfo culture)
117128
{
118-
129+
119130
foreach (var localizable in GetTranslatable(str))
120131
{
121132
var validIdentifier = LocalizationHelper.CreateId(localizable.CleanUpLocalizationTokens());
122133

123-
// Search in first level resource
124-
var translation = _resourceManager?.GetString(validIdentifier, culture);
125-
126-
// Search in parent resources
134+
// Search through all resource managers for the key
135+
string translation = null;
136+
foreach (var resourceManager in ResourceManagers)
137+
{
138+
translation = resourceManager?.GetString(validIdentifier, culture);
139+
if (translation != null)
140+
{
141+
break; // Found translation, stop searching
142+
}
143+
}
144+
145+
// Search in parent resources if not found
127146
if (translation == null)
128147
{
129148
try
130149
{
131-
return LocalizeInParents(str, twinElement, culture);
150+
translation = LocalizeInParents(localizable, twinElement, culture);
132151
}
133152
catch
134153
{
@@ -144,5 +163,20 @@ public string Localize(string str, ITwinElement twinElement, CultureInfo culture
144163

145164
return str;
146165
}
166+
167+
/// <summary>
168+
/// Sets the primary application resource for all translators.
169+
/// This would be tipycally set to the resource containing the application's translations.
170+
/// Any matching localization key will be first searched in this resource and then in the library resource.
171+
/// You can leverage this to override library translations with application specific ones.
172+
/// </summary>
173+
/// <param name="resourceType"></param>
174+
public static void SetPrimaryTranslatorResource(Type resourceType)
175+
{
176+
_applicationResourceManager = new ResourceManager(resourceType)
177+
{
178+
IgnoreCase = true
179+
};
180+
}
147181
}
148182
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace AXSharp.ConnectorTests.Localizations.Resources
2+
{
3+
/// <summary>
4+
/// Marker type used to locate the embedded resource `OverrideApplication.resx`.
5+
/// </summary>
6+
public class OverrideApplication
7+
{
8+
}
9+
}
10+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<root>
3+
<resheader name="resmimetype">
4+
<value>text/microsoft-resx</value>
5+
</resheader>
6+
<resheader name="version">
7+
<value>2.0</value>
8+
</resheader>
9+
<resheader name="reader">
10+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
11+
</resheader>
12+
<resheader name="writer">
13+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
14+
</resheader>
15+
<data name="Override_token" xml:space="preserve">
16+
<value>APP</value>
17+
</data>
18+
</root>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace AXSharp.ConnectorTests.Localizations.Resources
2+
{
3+
/// <summary>
4+
/// Marker type used to locate the embedded resource `OverrideLibrary.resx`.
5+
/// </summary>
6+
public class OverrideLibrary
7+
{
8+
}
9+
}
10+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<root>
3+
<resheader name="resmimetype">
4+
<value>text/microsoft-resx</value>
5+
</resheader>
6+
<resheader name="version">
7+
<value>2.0</value>
8+
</resheader>
9+
<resheader name="reader">
10+
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
11+
</resheader>
12+
<resheader name="writer">
13+
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
14+
</resheader>
15+
<data name="Override_token" xml:space="preserve">
16+
<value>LIB</value>
17+
</data>
18+
<data name="Library_only" xml:space="preserve">
19+
<value>LIB_ONLY</value>
20+
</data>
21+
</root>

src/AXSharp.connectors/tests/AXSharp.ConnectorTests/AXSharp.ConnectorTests/Localizations/TranslatorTests.cs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ namespace AXSharp.ConnectorTests.Localizations
22
{
33
using AXSharp.Connector.Localizations;
44
using System;
5+
using System.Globalization;
56
using Xunit;
67
using NSubstitute;
78
using AXSharp.Connector;
9+
using AXSharp.ConnectorTests.Localizations.Resources;
810

911
public class TranslatorTests
1012
{
@@ -53,6 +55,57 @@ public void CanCallSetLocalizationResource()
5355

5456
// Act
5557
_testClass.SetLocalizationResource(resourceType);
56-
}
58+
}
59+
60+
[Fact]
61+
public void Translate_prefers_primary_application_resource_over_library_resource()
62+
{
63+
// Arrange
64+
Translator.SetPrimaryTranslatorResource(typeof(OverrideApplication));
65+
var translator = new Translator();
66+
translator.SetLocalizationResource(typeof(OverrideLibrary));
67+
var twin = Substitute.For<ITwinElement>();
68+
var originalString = "<#Override token#>";
69+
70+
// Act
71+
var result = translator.Translate(originalString, twin, CultureInfo.InvariantCulture);
72+
73+
// Assert
74+
Assert.Equal("APP", result);
75+
}
76+
77+
[Fact]
78+
public void Translate_falls_back_to_library_resource_when_primary_does_not_have_key()
79+
{
80+
// Arrange
81+
Translator.SetPrimaryTranslatorResource(typeof(OverrideApplication));
82+
var translator = new Translator();
83+
translator.SetLocalizationResource(typeof(OverrideLibrary));
84+
var twin = Substitute.For<ITwinElement>();
85+
var originalString = "<#Library only#>";
86+
87+
// Act
88+
var result = translator.Translate(originalString, twin, CultureInfo.InvariantCulture);
89+
90+
// Assert
91+
Assert.Equal("LIB_ONLY", result);
92+
}
93+
94+
[Fact]
95+
public void Translate_when_key_missing_returns_text_without_localization_tokens()
96+
{
97+
// Arrange
98+
Translator.SetPrimaryTranslatorResource(typeof(OverrideApplication));
99+
var translator = new Translator();
100+
translator.SetLocalizationResource(typeof(OverrideLibrary));
101+
var twin = Substitute.For<ITwinElement>();
102+
var originalString = "<#Does not exist#>";
103+
104+
// Act
105+
var result = translator.Translate(originalString, twin, CultureInfo.InvariantCulture);
106+
107+
// Assert
108+
Assert.Equal("Does not exist", result);
109+
}
57110
}
58111
}

0 commit comments

Comments
 (0)