Skip to content

Commit 18b3cb0

Browse files
committed
Add app-level override for localization resources
Enable application resource to override library translations in Translator. Add SetPrimaryTranslatorResource method and update translation logic. Include unit tests and resource files to verify override and fallback behavior.
1 parent 560fff4 commit 18b3cb0

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)