diff --git a/src/Umbraco.Core/Models/UserExtensions.cs b/src/Umbraco.Core/Models/UserExtensions.cs
index 0621c83a72..99f72b8fb8 100644
--- a/src/Umbraco.Core/Models/UserExtensions.cs
+++ b/src/Umbraco.Core/Models/UserExtensions.cs
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.Linq;
+using System.Threading;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
@@ -23,13 +24,21 @@ namespace Umbraco.Core.Models
internal static CultureInfo GetUserCulture(string userLanguage, ILocalizedTextService textService)
{
- return textService.GetSupportedCultures()
- .FirstOrDefault(culture =>
- //match on full name first
- culture.Name.InvariantEquals(userLanguage.Replace("_", "-")) ||
- //then match on the 2 letter name
- culture.TwoLetterISOLanguageName.InvariantEquals(userLanguage));
- }
+ try
+ {
+ var culture = CultureInfo.GetCultureInfo(userLanguage);
+ //TODO: This is a hack because we store the user language as 2 chars instead of the full culture
+ // which is actually stored in the language files (which are also named with 2 chars!) so we need to attempt
+ // to convert to a supported full culture
+ var result = textService.ConvertToSupportedCultureWithRegionCode(culture);
+ return result;
+ }
+ catch (CultureNotFoundException)
+ {
+ //return the default one
+ return CultureInfo.GetCultureInfo("en");
+ }
+ }
///
/// Checks if the user has access to the content item based on their start noe
diff --git a/src/Umbraco.Core/Services/ILocalizedTextService.cs b/src/Umbraco.Core/Services/ILocalizedTextService.cs
index de95f24efa..9875d63170 100644
--- a/src/Umbraco.Core/Services/ILocalizedTextService.cs
+++ b/src/Umbraco.Core/Services/ILocalizedTextService.cs
@@ -33,5 +33,19 @@ namespace Umbraco.Core.Services
///
///
IEnumerable GetSupportedCultures();
+
+ ///
+ /// Tries to resolve a full 4 letter culture from a 2 letter culture name
+ ///
+ ///
+ /// The culture to determine if it is only a 2 letter culture, if so we'll try to convert it, otherwise it will just be returned
+ ///
+ ///
+ ///
+ /// TODO: This is just a hack due to the way we store the language files, they should be stored with 4 letters since that
+ /// is what they reference but they are stored with 2, further more our user's languages are stored with 2. So this attempts
+ /// to resolve the full culture if possible.
+ ///
+ CultureInfo ConvertToSupportedCultureWithRegionCode(CultureInfo currentCulture);
}
}
diff --git a/src/Umbraco.Core/Services/LocalizedTextService.cs b/src/Umbraco.Core/Services/LocalizedTextService.cs
index c9d05f3a3b..68c1c8d5e1 100644
--- a/src/Umbraco.Core/Services/LocalizedTextService.cs
+++ b/src/Umbraco.Core/Services/LocalizedTextService.cs
@@ -13,6 +13,7 @@ namespace Umbraco.Core.Services
public class LocalizedTextService : ILocalizedTextService
{
+ private readonly LocalizedTextServiceFileSources _fileSources;
private readonly IDictionary>> _dictionarySource;
private readonly IDictionary> _xmlSource;
@@ -22,7 +23,8 @@ namespace Umbraco.Core.Services
///
public LocalizedTextService(LocalizedTextServiceFileSources fileSources)
{
- _xmlSource = fileSources.GetXmlSources();
+ if (fileSources == null) throw new ArgumentNullException("fileSources");
+ _fileSources = fileSources;
}
///
@@ -49,6 +51,9 @@ namespace Umbraco.Core.Services
{
Mandate.ParameterNotNull(culture, "culture");
+ //TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
+ culture = ConvertToSupportedCultureWithRegionCode(culture);
+
//This is what the legacy ui service did
if (string.IsNullOrEmpty(key))
return string.Empty;
@@ -57,10 +62,14 @@ namespace Umbraco.Core.Services
var area = keyParts.Length > 1 ? keyParts[0] : null;
var alias = keyParts.Length > 1 ? keyParts[1] : keyParts[0];
- if (_xmlSource != null)
+ var xmlSource = _xmlSource ?? (_fileSources != null
+ ? _fileSources.GetXmlSources()
+ : null);
+
+ if (xmlSource != null)
{
- return GetFromXmlSource(culture, area, alias, tokens);
- }
+ return GetFromXmlSource(xmlSource, culture, area, alias, tokens);
+ }
else
{
return GetFromDictionarySource(culture, area, alias, tokens);
@@ -75,18 +84,25 @@ namespace Umbraco.Core.Services
{
if (culture == null) throw new ArgumentNullException("culture");
+ //TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
+ culture = ConvertToSupportedCultureWithRegionCode(culture);
+
var result = new Dictionary();
- if (_xmlSource != null)
+ var xmlSource = _xmlSource ?? (_fileSources != null
+ ? _fileSources.GetXmlSources()
+ : null);
+
+ if (xmlSource != null)
{
- if (_xmlSource.ContainsKey(culture) == false)
+ if (xmlSource.ContainsKey(culture) == false)
{
LogHelper.Warn("The culture specified {0} was not found in any configured sources for this service", () => culture);
return result;
}
//convert all areas + keys to a single key with a '/'
- var areas = _xmlSource[culture].Value.XPathSelectElements("//area");
+ var areas = xmlSource[culture].Value.XPathSelectElements("//area");
foreach (var area in areas)
{
var keys = area.XPathSelectElements("./key");
@@ -133,7 +149,36 @@ namespace Umbraco.Core.Services
///
public IEnumerable GetSupportedCultures()
{
- return _xmlSource != null ? _xmlSource.Keys : _dictionarySource.Keys;
+ var xmlSource = _xmlSource ?? (_fileSources != null
+ ? _fileSources.GetXmlSources()
+ : null);
+
+ return xmlSource != null ? xmlSource.Keys : _dictionarySource.Keys;
+ }
+
+ ///
+ /// Tries to resolve a full 4 letter culture from a 2 letter culture name
+ ///
+ ///
+ /// The culture to determine if it is only a 2 letter culture, if so we'll try to convert it, otherwise it will just be returned
+ ///
+ ///
+ ///
+ /// TODO: This is just a hack due to the way we store the language files, they should be stored with 4 letters since that
+ /// is what they reference but they are stored with 2, further more our user's languages are stored with 2. So this attempts
+ /// to resolve the full culture if possible.
+ ///
+ /// This only works when this service is constructed with the LocalizedTextServiceFileSources
+ ///
+ public CultureInfo ConvertToSupportedCultureWithRegionCode(CultureInfo currentCulture)
+ {
+ if (currentCulture == null) throw new ArgumentNullException("currentCulture");
+
+ if (_fileSources == null) return currentCulture;
+ if (currentCulture.Name.Length > 2) return currentCulture;
+
+ var attempt = _fileSources.TryConvert2LetterCultureTo4Letter(currentCulture.TwoLetterISOLanguageName);
+ return attempt ? attempt.Result : currentCulture;
}
private string GetFromDictionarySource(CultureInfo culture, string area, string key, IDictionary tokens)
@@ -174,15 +219,15 @@ namespace Umbraco.Core.Services
return "[" + key + "]";
}
- private string GetFromXmlSource(CultureInfo culture, string area, string key, IDictionary tokens)
+ private static string GetFromXmlSource(IDictionary> xmlSource, CultureInfo culture, string area, string key, IDictionary tokens)
{
- if (_xmlSource.ContainsKey(culture) == false)
+ if (xmlSource.ContainsKey(culture) == false)
{
LogHelper.Warn("The culture specified {0} was not found in any configured sources for this service", () => culture);
return "[" + key + "]";
}
- var cultureSource = _xmlSource[culture].Value;
+ var cultureSource = xmlSource[culture].Value;
var xpath = area.IsNullOrWhiteSpace()
? string.Format("//key [@alias = '{0}']", key)
@@ -213,7 +258,7 @@ namespace Umbraco.Core.Services
/// we support a dictionary which means in the future we can really have any sort of token system.
/// Currently though, the token key's will need to be an integer and sequential - though we aren't going to throw exceptions if that is not the case.
///
- internal string ParseTokens(string value, IDictionary tokens)
+ internal static string ParseTokens(string value, IDictionary tokens)
{
if (tokens == null || tokens.Any() == false)
{
diff --git a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs
index 30e43a694c..be53d8d16b 100644
--- a/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs
+++ b/src/Umbraco.Core/Services/LocalizedTextServiceFileSources.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
+using System.Xml;
using System.Xml.Linq;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
@@ -16,6 +17,9 @@ namespace Umbraco.Core.Services
private readonly IRuntimeCacheProvider _cache;
private readonly DirectoryInfo _fileSourceFolder;
+ //TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
+ private readonly Dictionary _twoLetterCultureConverter = new Dictionary();
+
public LocalizedTextServiceFileSources(IRuntimeCacheProvider cache, DirectoryInfo fileSourceFolder)
{
if (cache == null) throw new ArgumentNullException("cache");
@@ -46,10 +50,53 @@ namespace Umbraco.Core.Services
{
var localCopy = fileInfo;
var filename = Path.GetFileNameWithoutExtension(localCopy.FullName).Replace("_", "-");
- var culture = CultureInfo.GetCultureInfo(filename);
+
+ //TODO: Fix this nonsense... would have to wait until v8 to store the language files with their correct
+ // names instead of storing them as 2 letters but actually having a 4 letter culture. wtf. So now, we
+ // need to check if the file is 2 letters, then open it to try to find it's 4 letter culture, then use that
+ // if it's successful. We're going to assume (though it seems assuming in the legacy logic is never a great idea)
+ // that any 4 letter file is named with the actual culture that it is!
+ CultureInfo culture = null;
+ if (filename.Length == 2)
+ {
+ //we need to open the file to see if we can read it's 'real' culture, we'll use XmlReader since we don't
+ //want to load in the entire doc into mem just to read a single value
+ using (var fs = fileInfo.OpenRead())
+ using (var reader = XmlReader.Create(fs))
+ {
+ if (reader.IsStartElement())
+ {
+ if (reader.Name == "language")
+ {
+ if (reader.MoveToAttribute("culture"))
+ {
+ var cultureVal = reader.Value;
+ try
+ {
+ culture = CultureInfo.GetCultureInfo(cultureVal);
+ //add to the tracked dictionary
+ _twoLetterCultureConverter[filename] = culture;
+ }
+ catch (CultureNotFoundException)
+ {
+ LogHelper.Warn(
+ string.Format("The culture {0} found in the file {1} is not a valid culture", cultureVal, fileInfo.FullName));
+ //If the culture in the file is invalid, we'll just hope the file name is a valid culture below, otherwise
+ // an exception will be thrown.
+ }
+ }
+ }
+ }
+ }
+ }
+ if (culture == null)
+ {
+ culture = CultureInfo.GetCultureInfo(filename);
+ }
+
//get the lazy value from cache
result.Add(culture, new Lazy(() => _cache.GetCacheItem(
- string.Format("{0}-{1}", typeof (LocalizedTextServiceFileSources).Name, culture.TwoLetterISOLanguageName), () =>
+ string.Format("{0}-{1}", typeof (LocalizedTextServiceFileSources).Name, culture.Name), () =>
{
using (var fs = localCopy.OpenRead())
{
@@ -59,5 +106,15 @@ namespace Umbraco.Core.Services
}
return result;
}
+
+ //TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
+ public Attempt TryConvert2LetterCultureTo4Letter(string twoLetterCulture)
+ {
+ if (twoLetterCulture.Length != 2) Attempt.Fail();
+
+ return _twoLetterCultureConverter.ContainsKey(twoLetterCulture)
+ ? Attempt.Succeed(_twoLetterCultureConverter[twoLetterCulture])
+ : Attempt.Fail();
+ }
}
}
\ No newline at end of file