using System; using System.Globalization; using System.Runtime.InteropServices; using System.Text; namespace DataUtils { internal static class NativeMethods { // GetLocaleInfoW for LCID-based queries [DllImport ("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern int GetLocaleInfoW (int Locale, int LCType, [Out] StringBuilder lpLCData, int cchData); // GetLocaleInfoEx for locale name based queries [DllImport ("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern int GetLocaleInfoEx (string lpLocaleName, int LCType, [Out] StringBuilder lpLCData, int cchData); // LocaleNameToLCID - available on Vista+; fallback is to use CultureInfo [DllImport ("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern int LocaleNameToLCID (string lpName, uint dwFlags); // LCIDToLocaleName (Vista+) [DllImport ("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern int LCIDToLocaleName (int Locale, [Out] StringBuilder lpName, int cchName, uint dwFlags); } [ComVisible (true)] [ClassInterface (ClassInterfaceType.AutoDual)] public class _I_Locale { // Current locale name like "en-US" public string CurrentLocale { get { try { // prefer thread culture name which reflects user culture string name = CultureInfo.CurrentCulture.Name; if (string.IsNullOrEmpty (name)) name = CultureInfo.InstalledUICulture.Name; return name ?? string.Empty; } catch { return string.Empty; } } } // Current LCID (int) public int CurrentLCID { get { try { return CultureInfo.CurrentCulture.LCID; } catch { return CultureInfo.InvariantCulture.LCID; } } } // Convert LCID -> locale name (e.g. 1033 -> "en-US") public string ToLocaleName (int lcid) { try { // Try managed first var ci = new CultureInfo (lcid); if (!string.IsNullOrEmpty (ci.Name)) return ci.Name; } catch { // try Win32 LCIDToLocaleName (Vista+) try { StringBuilder sb = new StringBuilder (LOCALE_NAME_MAX_LENGTH); int res = NativeMethods.LCIDToLocaleName (lcid, sb, sb.Capacity, 0); if (res > 0) return sb.ToString (); } catch { } } return string.Empty; } // Convert locale name -> LCID public int ToLCID (string localeName) { if (string.IsNullOrEmpty (localeName)) return CultureInfo.InvariantCulture.LCID; try { // prefer managed creation var ci = new CultureInfo (localeName); return ci.LCID; } catch { // try Win32 LocaleNameToLCID (Vista+) try { int lcid = NativeMethods.LocaleNameToLCID (localeName, 0); if (lcid != 0) return lcid; } catch { } } // fallback: invariant culture return CultureInfo.InvariantCulture.LCID; } // Return a locale info string for given LCID and LCTYPE. LCTYPE is the Win32 LOCALE_* constant. // Returns a string (or empty string on failure). public object LocaleInfo (int lcid, int lctype) { try { // First try mapping common LCTYPE values to managed properties for better correctness // Some common LCTYPE values: // LOCALE_SISO639LANGNAME = 0x59 (89) -> Two-letter ISO language name // LOCALE_SISO3166CTRYNAME = 0x5A (90) -> Two-letter country/region name // LOCALE_SNAME = 0x5c (92) -> locale name like "en-US" (Vista+) // But we cannot rely on all values, so we fallback to native GetLocaleInfoW. if (lctype == 0x59) // LOCALE_SISO639LANGNAME { try { var ci = new CultureInfo (lcid); return ci.TwoLetterISOLanguageName ?? string.Empty; } catch { } } else if (lctype == 0x5A) // LOCALE_SISO3166CTRYNAME { try { var ci = new CultureInfo (lcid); try { var ri = new RegionInfo (ci.Name); return ri.TwoLetterISORegionName ?? string.Empty; } catch { // some cultures have no region; fallback to parsing name var name = ci.Name; if (!string.IsNullOrEmpty (name) && name.IndexOf ('-') >= 0) { return name.Split ('-') [1]; } } } catch { } } else if (lctype == 0x5c) // LOCALE_SNAME { try { var ci = new CultureInfo (lcid); return ci.Name ?? string.Empty; } catch { } } // Fallback to native GetLocaleInfoW StringBuilder sb = new StringBuilder (256); int ret = NativeMethods.GetLocaleInfoW (lcid, lctype, sb, sb.Capacity); if (ret > 0) return sb.ToString (); return string.Empty; } catch { return string.Empty; } } // LocaleInfoEx: query by locale name string and LCTYPE // Returns string if available; otherwise returns the integer result code (as int) if string empty (mimic C++ behavior). public object LocaleInfoEx (string localeName, int lctype) { if (string.IsNullOrEmpty (localeName)) { // fall back to current culture name localeName = CurrentLocale; if (string.IsNullOrEmpty (localeName)) return 0; } try { // Try managed shortcuts for common types if (lctype == 0x59) // LOCALE_SISO639LANGNAME { try { var ci = new CultureInfo (localeName); return ci.TwoLetterISOLanguageName ?? string.Empty; } catch { } } else if (lctype == 0x5A) // LOCALE_SISO3166CTRYNAME { try { var ci = new CultureInfo (localeName); var ri = new RegionInfo (ci.Name); return ri.TwoLetterISORegionName ?? string.Empty; } catch { // try to split var parts = localeName.Split (new char [] { '-', '_' }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length >= 2) return parts [1]; } } else if (lctype == 0x5c) // LOCALE_SNAME { // localeName is probably already the name return localeName; } // Fallback to GetLocaleInfoEx StringBuilder sb = new StringBuilder (LOCALE_NAME_MAX_LENGTH); int res = NativeMethods.GetLocaleInfoEx (localeName, lctype, sb, sb.Capacity); if (res > 0) { string outStr = sb.ToString (); if (!string.IsNullOrEmpty (outStr)) return outStr; } // if nothing returned, return the result code return res; } catch { return 0; } } // Helpers similar to the C++: restricted (language) and elaborated (region) codes public string GetLocaleRestrictedCode (string localeName) { if (string.IsNullOrEmpty (localeName)) localeName = CurrentLocale; try { var ci = new CultureInfo (localeName); return ci.TwoLetterISOLanguageName ?? string.Empty; } catch { // fallback: parse name var parts = localeName.Split (new char [] { '-', '_' }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length >= 1) return parts [0]; return string.Empty; } } public string GetLocaleElaboratedCode (string localeName) { if (string.IsNullOrEmpty (localeName)) localeName = CurrentLocale; try { var ci = new CultureInfo (localeName); // Region part from RegionInfo try { var ri = new RegionInfo (ci.Name); return ri.TwoLetterISORegionName ?? string.Empty; } catch { // fallback: parse var parts = localeName.Split (new char [] { '-', '_' }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length >= 2) return parts [1]; } } catch { var parts = localeName.Split (new char [] { '-', '_' }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length >= 2) return parts [1]; } return string.Empty; } // LCID -> combined code like "en-US" (with configurable separator) public string LcidToLocaleCode (int lcid) { try { var ci = new CultureInfo (lcid); if (!string.IsNullOrEmpty (ci.Name)) return ci.Name; } catch { try { var name = ToLocaleName (lcid); if (!string.IsNullOrEmpty (name)) return name; } catch { } } return string.Empty; } // Get the user default locale name public string GetUserDefaultLocaleName () { try { // In .NET, CurrentCulture corresponds to user default string name = CultureInfo.CurrentCulture.Name; if (!string.IsNullOrEmpty (name)) return name; } catch { } return LcidToLocaleCode (CultureInfo.CurrentCulture.LCID); } // Get system default locale name (machine) public string GetSystemDefaultLocaleName () { try { // InstalledUICulture / Invariant fallback string name = CultureInfo.InstalledUICulture.Name; if (!string.IsNullOrEmpty (name)) return name; } catch { } return LcidToLocaleCode (CultureInfo.InstalledUICulture.LCID); } // Get computer locale code similar to C++ approach public string GetComputerLocaleCode () { try { // Thread culture -> user -> system string threadName = System.Threading.Thread.CurrentThread.CurrentCulture.Name; if (!string.IsNullOrEmpty (threadName)) return threadName; string user = GetUserDefaultLocaleName (); if (!string.IsNullOrEmpty (user)) return user; string system = GetSystemDefaultLocaleName (); if (!string.IsNullOrEmpty (system)) return system; } catch { } // fallback to invariant return CultureInfo.InvariantCulture.Name ?? string.Empty; } // Compare two locale names; returns true if equal by name or LCID public bool LocaleNameCompare (string left, string right) { if (string.Equals (left, right, StringComparison.OrdinalIgnoreCase)) return true; try { int l = ToLCID (left); int r = ToLCID (right); return l == r && l != CultureInfo.InvariantCulture.LCID; } catch { return false; } } // Constants private const int LOCALE_NAME_MAX_LENGTH = 85; // defined by Windows } }