mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Introduce ILocalizationStreamParser
This moves the logic of parsing PO files from DefaultLocalizedStringManager to a separate service, so that parsing can be tested independently from resolving translations from loaded PO files.
This commit is contained in:
@@ -1,8 +1,5 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
using Orchard.Caching;
|
using Orchard.Caching;
|
||||||
using Orchard.Environment.Configuration;
|
using Orchard.Environment.Configuration;
|
||||||
using Orchard.Environment.Extensions;
|
using Orchard.Environment.Extensions;
|
||||||
@@ -15,8 +12,10 @@ namespace Orchard.Localization.Services {
|
|||||||
private readonly IWebSiteFolder _webSiteFolder;
|
private readonly IWebSiteFolder _webSiteFolder;
|
||||||
private readonly IExtensionManager _extensionManager;
|
private readonly IExtensionManager _extensionManager;
|
||||||
private readonly ICacheManager _cacheManager;
|
private readonly ICacheManager _cacheManager;
|
||||||
|
private readonly ILocalizationStreamParser _localizationStreamParser;
|
||||||
private readonly ShellSettings _shellSettings;
|
private readonly ShellSettings _shellSettings;
|
||||||
private readonly ISignals _signals;
|
private readonly ISignals _signals;
|
||||||
|
|
||||||
const string CoreLocalizationFilePathFormat = "~/Core/App_Data/Localization/{0}/orchard.core.po";
|
const string CoreLocalizationFilePathFormat = "~/Core/App_Data/Localization/{0}/orchard.core.po";
|
||||||
const string ModulesLocalizationFilePathFormat = "~/Modules/{0}/App_Data/Localization/{1}/orchard.module.po";
|
const string ModulesLocalizationFilePathFormat = "~/Modules/{0}/App_Data/Localization/{1}/orchard.module.po";
|
||||||
const string ThemesLocalizationFilePathFormat = "~/Themes/{0}/App_Data/Localization/{1}/orchard.theme.po";
|
const string ThemesLocalizationFilePathFormat = "~/Themes/{0}/App_Data/Localization/{1}/orchard.theme.po";
|
||||||
@@ -27,11 +26,13 @@ namespace Orchard.Localization.Services {
|
|||||||
IWebSiteFolder webSiteFolder,
|
IWebSiteFolder webSiteFolder,
|
||||||
IExtensionManager extensionManager,
|
IExtensionManager extensionManager,
|
||||||
ICacheManager cacheManager,
|
ICacheManager cacheManager,
|
||||||
|
ILocalizationStreamParser locationStreamParser,
|
||||||
ShellSettings shellSettings,
|
ShellSettings shellSettings,
|
||||||
ISignals signals) {
|
ISignals signals) {
|
||||||
_webSiteFolder = webSiteFolder;
|
_webSiteFolder = webSiteFolder;
|
||||||
_extensionManager = extensionManager;
|
_extensionManager = extensionManager;
|
||||||
_cacheManager = cacheManager;
|
_cacheManager = cacheManager;
|
||||||
|
_localizationStreamParser = locationStreamParser;
|
||||||
_shellSettings = shellSettings;
|
_shellSettings = shellSettings;
|
||||||
_signals = signals;
|
_signals = signals;
|
||||||
|
|
||||||
@@ -114,7 +115,7 @@ namespace Orchard.Localization.Services {
|
|||||||
string corePath = string.Format(CoreLocalizationFilePathFormat, culture);
|
string corePath = string.Format(CoreLocalizationFilePathFormat, culture);
|
||||||
string text = _webSiteFolder.ReadFile(corePath);
|
string text = _webSiteFolder.ReadFile(corePath);
|
||||||
if (text != null) {
|
if (text != null) {
|
||||||
ParseLocalizationStream(text, translations, false);
|
_localizationStreamParser.ParseLocalizationStream(text, translations, false);
|
||||||
if (!DisableMonitoring) {
|
if (!DisableMonitoring) {
|
||||||
Logger.Debug("Monitoring virtual path \"{0}\"", corePath);
|
Logger.Debug("Monitoring virtual path \"{0}\"", corePath);
|
||||||
context.Monitor(_webSiteFolder.WhenPathChanges(corePath));
|
context.Monitor(_webSiteFolder.WhenPathChanges(corePath));
|
||||||
@@ -126,7 +127,7 @@ namespace Orchard.Localization.Services {
|
|||||||
string modulePath = string.Format(ModulesLocalizationFilePathFormat, module.Id, culture);
|
string modulePath = string.Format(ModulesLocalizationFilePathFormat, module.Id, culture);
|
||||||
text = _webSiteFolder.ReadFile(modulePath);
|
text = _webSiteFolder.ReadFile(modulePath);
|
||||||
if (text != null) {
|
if (text != null) {
|
||||||
ParseLocalizationStream(text, translations, true);
|
_localizationStreamParser.ParseLocalizationStream(text, translations, true);
|
||||||
|
|
||||||
if (!DisableMonitoring) {
|
if (!DisableMonitoring) {
|
||||||
Logger.Debug("Monitoring virtual path \"{0}\"", modulePath);
|
Logger.Debug("Monitoring virtual path \"{0}\"", modulePath);
|
||||||
@@ -141,7 +142,7 @@ namespace Orchard.Localization.Services {
|
|||||||
string themePath = string.Format(ThemesLocalizationFilePathFormat, theme.Id, culture);
|
string themePath = string.Format(ThemesLocalizationFilePathFormat, theme.Id, culture);
|
||||||
text = _webSiteFolder.ReadFile(themePath);
|
text = _webSiteFolder.ReadFile(themePath);
|
||||||
if (text != null) {
|
if (text != null) {
|
||||||
ParseLocalizationStream(text, translations, true);
|
_localizationStreamParser.ParseLocalizationStream(text, translations, true);
|
||||||
|
|
||||||
if (!DisableMonitoring) {
|
if (!DisableMonitoring) {
|
||||||
Logger.Debug("Monitoring virtual path \"{0}\"", themePath);
|
Logger.Debug("Monitoring virtual path \"{0}\"", themePath);
|
||||||
@@ -154,7 +155,7 @@ namespace Orchard.Localization.Services {
|
|||||||
string rootPath = string.Format(RootLocalizationFilePathFormat, culture);
|
string rootPath = string.Format(RootLocalizationFilePathFormat, culture);
|
||||||
text = _webSiteFolder.ReadFile(rootPath);
|
text = _webSiteFolder.ReadFile(rootPath);
|
||||||
if (text != null) {
|
if (text != null) {
|
||||||
ParseLocalizationStream(text, translations, true);
|
_localizationStreamParser.ParseLocalizationStream(text, translations, true);
|
||||||
if (!DisableMonitoring) {
|
if (!DisableMonitoring) {
|
||||||
Logger.Debug("Monitoring virtual path \"{0}\"", rootPath);
|
Logger.Debug("Monitoring virtual path \"{0}\"", rootPath);
|
||||||
context.Monitor(_webSiteFolder.WhenPathChanges(rootPath));
|
context.Monitor(_webSiteFolder.WhenPathChanges(rootPath));
|
||||||
@@ -164,7 +165,7 @@ namespace Orchard.Localization.Services {
|
|||||||
string tenantPath = string.Format(TenantLocalizationFilePathFormat, _shellSettings.Name, culture);
|
string tenantPath = string.Format(TenantLocalizationFilePathFormat, _shellSettings.Name, culture);
|
||||||
text = _webSiteFolder.ReadFile(tenantPath);
|
text = _webSiteFolder.ReadFile(tenantPath);
|
||||||
if (text != null) {
|
if (text != null) {
|
||||||
ParseLocalizationStream(text, translations, true);
|
_localizationStreamParser.ParseLocalizationStream(text, translations, true);
|
||||||
if (!DisableMonitoring) {
|
if (!DisableMonitoring) {
|
||||||
Logger.Debug("Monitoring virtual path \"{0}\"", tenantPath);
|
Logger.Debug("Monitoring virtual path \"{0}\"", tenantPath);
|
||||||
context.Monitor(_webSiteFolder.WhenPathChanges(tenantPath));
|
context.Monitor(_webSiteFolder.WhenPathChanges(tenantPath));
|
||||||
@@ -174,110 +175,6 @@ namespace Orchard.Localization.Services {
|
|||||||
return translations;
|
return translations;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Dictionary<char, char> _escapeTranslations = new Dictionary<char, char> {
|
|
||||||
{ 'n', '\n' },
|
|
||||||
{ 'r', '\r' },
|
|
||||||
{ 't', '\t' }
|
|
||||||
};
|
|
||||||
|
|
||||||
private static string Unescape(string str) {
|
|
||||||
StringBuilder sb = null;
|
|
||||||
bool escaped = false;
|
|
||||||
for (var i = 0; i < str.Length; i++) {
|
|
||||||
var c = str[i];
|
|
||||||
if (escaped) {
|
|
||||||
if (sb == null) {
|
|
||||||
sb = new StringBuilder(str.Length);
|
|
||||||
if (i > 1) {
|
|
||||||
sb.Append(str.Substring(0, i - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
char unescaped;
|
|
||||||
if (_escapeTranslations.TryGetValue(c, out unescaped)) {
|
|
||||||
sb.Append(unescaped);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// General rule: \x ==> x
|
|
||||||
sb.Append(c);
|
|
||||||
}
|
|
||||||
escaped = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (c == '\\') {
|
|
||||||
escaped = true;
|
|
||||||
}
|
|
||||||
else if (sb != null) {
|
|
||||||
sb.Append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb == null ? str : sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ParseLocalizationStream(string text, IDictionary<string, string> translations, bool merge) {
|
|
||||||
StringReader reader = new StringReader(text);
|
|
||||||
string poLine, id, scope;
|
|
||||||
id = scope = String.Empty;
|
|
||||||
while ((poLine = reader.ReadLine()) != null) {
|
|
||||||
if (poLine.StartsWith("#:")) {
|
|
||||||
scope = ParseScope(poLine);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (poLine.StartsWith("msgctxt")) {
|
|
||||||
scope = ParseContext(poLine);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (poLine.StartsWith("msgid")) {
|
|
||||||
id = ParseId(poLine);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (poLine.StartsWith("msgstr")) {
|
|
||||||
string translation = ParseTranslation(poLine);
|
|
||||||
// ignore incomplete localizations (empty msgid or msgstr)
|
|
||||||
if (!String.IsNullOrWhiteSpace(id) && !String.IsNullOrWhiteSpace(translation)) {
|
|
||||||
string scopedKey = (scope + "|" + id).ToLowerInvariant();
|
|
||||||
if (!translations.ContainsKey(scopedKey)) {
|
|
||||||
translations.Add(scopedKey, translation);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (merge) {
|
|
||||||
translations[scopedKey] = translation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
id = scope = String.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string TrimQuote(string str) {
|
|
||||||
if (str.StartsWith("\"") && str.EndsWith("\"")) {
|
|
||||||
return str.Substring(1, str.Length - 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ParseTranslation(string poLine) {
|
|
||||||
return Unescape(TrimQuote(poLine.Substring(6).Trim()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ParseId(string poLine) {
|
|
||||||
return Unescape(TrimQuote(poLine.Substring(5).Trim()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ParseScope(string poLine) {
|
|
||||||
return Unescape(TrimQuote(poLine.Substring(2).Trim()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ParseContext(string poLine) {
|
|
||||||
return Unescape(TrimQuote(poLine.Substring(7).Trim()));
|
|
||||||
}
|
|
||||||
|
|
||||||
class CultureDictionary {
|
class CultureDictionary {
|
||||||
public string CultureName { get; set; }
|
public string CultureName { get; set; }
|
||||||
public IDictionary<string, string> Translations { get; set; }
|
public IDictionary<string, string> Translations { get; set; }
|
||||||
|
@@ -0,0 +1,7 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Orchard.Localization.Services {
|
||||||
|
public interface ILocalizationStreamParser : IDependency {
|
||||||
|
void ParseLocalizationStream(string text, IDictionary<string, string> translations, bool merge);
|
||||||
|
}
|
||||||
|
}
|
114
src/Orchard/Localization/Services/LocalizationStreamParser.cs
Normal file
114
src/Orchard/Localization/Services/LocalizationStreamParser.cs
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Orchard.Localization.Services {
|
||||||
|
|
||||||
|
public class LocalizationStreamParser : ILocalizationStreamParser {
|
||||||
|
|
||||||
|
private static readonly Dictionary<char, char> _escapeTranslations = new Dictionary<char, char> {
|
||||||
|
{ 'n', '\n' },
|
||||||
|
{ 'r', '\r' },
|
||||||
|
{ 't', '\t' }
|
||||||
|
};
|
||||||
|
|
||||||
|
public void ParseLocalizationStream(string text, IDictionary<string, string> translations, bool merge) {
|
||||||
|
StringReader reader = new StringReader(text);
|
||||||
|
string poLine, id, scope;
|
||||||
|
id = scope = String.Empty;
|
||||||
|
while ((poLine = reader.ReadLine()) != null) {
|
||||||
|
if (poLine.StartsWith("#:")) {
|
||||||
|
scope = ParseScope(poLine);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poLine.StartsWith("msgctxt")) {
|
||||||
|
scope = ParseContext(poLine);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poLine.StartsWith("msgid")) {
|
||||||
|
id = ParseId(poLine);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poLine.StartsWith("msgstr")) {
|
||||||
|
string translation = ParseTranslation(poLine);
|
||||||
|
// ignore incomplete localizations (empty msgid or msgstr)
|
||||||
|
if (!String.IsNullOrWhiteSpace(id) && !String.IsNullOrWhiteSpace(translation)) {
|
||||||
|
string scopedKey = (scope + "|" + id).ToLowerInvariant();
|
||||||
|
if (!translations.ContainsKey(scopedKey)) {
|
||||||
|
translations.Add(scopedKey, translation);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (merge) {
|
||||||
|
translations[scopedKey] = translation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
id = scope = String.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Unescape(string str) {
|
||||||
|
StringBuilder sb = null;
|
||||||
|
bool escaped = false;
|
||||||
|
for (var i = 0; i < str.Length; i++) {
|
||||||
|
var c = str[i];
|
||||||
|
if (escaped) {
|
||||||
|
if (sb == null) {
|
||||||
|
sb = new StringBuilder(str.Length);
|
||||||
|
if (i > 1) {
|
||||||
|
sb.Append(str.Substring(0, i - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
char unescaped;
|
||||||
|
if (_escapeTranslations.TryGetValue(c, out unescaped)) {
|
||||||
|
sb.Append(unescaped);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// General rule: \x ==> x
|
||||||
|
sb.Append(c);
|
||||||
|
}
|
||||||
|
escaped = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (c == '\\') {
|
||||||
|
escaped = true;
|
||||||
|
}
|
||||||
|
else if (sb != null) {
|
||||||
|
sb.Append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb == null ? str : sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string TrimQuote(string str) {
|
||||||
|
if (str.StartsWith("\"") && str.EndsWith("\"")) {
|
||||||
|
return str.Substring(1, str.Length - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ParseTranslation(string poLine) {
|
||||||
|
return Unescape(TrimQuote(poLine.Substring(6).Trim()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ParseId(string poLine) {
|
||||||
|
return Unescape(TrimQuote(poLine.Substring(5).Trim()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ParseScope(string poLine) {
|
||||||
|
return Unescape(TrimQuote(poLine.Substring(2).Trim()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ParseContext(string poLine) {
|
||||||
|
return Unescape(TrimQuote(poLine.Substring(7).Trim()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -148,6 +148,8 @@
|
|||||||
<Reference Include="System.Xml.Linq" />
|
<Reference Include="System.Xml.Linq" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="Localization\Services\ILocalizationStreamParser.cs" />
|
||||||
|
<Compile Include="Localization\Services\LocalizationStreamParser.cs" />
|
||||||
<Compile Include="Security\ISslSettingsProvider.cs" />
|
<Compile Include="Security\ISslSettingsProvider.cs" />
|
||||||
<Compile Include="Security\Providers\DefaultSslSettingsProvider.cs" />
|
<Compile Include="Security\Providers\DefaultSslSettingsProvider.cs" />
|
||||||
<Compile Include="StaticHttpContextScope.cs" />
|
<Compile Include="StaticHttpContextScope.cs" />
|
||||||
|
Reference in New Issue
Block a user