unit Application.Locale;
 
interface
 
type
{$REGION 'Locale'}
  /// <summary> Handles the current system locale settings </summary>
  /// <remarks> This is an "ambient-context" (see more at Dependency Injection in .NET by Mark Seemann)
  /// The actual work is delegated to a ILocaleSettings interface that can be overriden using the
  /// Locale.Current static property. A default implementation is used if the property is not set </remarks>
  Locale = record
  public type
{$REGION 'ILocaleSettings'}
    /// <summary> Handles the current system locale settings </summary>
    ILocaleSettings = interface
      ['{226AF252-8EB9-47D5-9D3A-EFC8692EDF38}']
      function GetCurrencyDecimalSeparator: System.Char;
      /// <summary> Returns wether the given Char is valid to be used in the string representation of
      /// Integer numbers </summary>
      /// <param name="AllowNegative"> Allows a '-' character to be treated as valid </param>
      /// <param name="AllowSpace"> Allows a ' ' character to be treated as valid </param>
      function IsIntegerChar(
        const Char: System.Char;
        const AllowNegative: Boolean = True;
        const AllowSpace: Boolean = True): Boolean;
      /// <summary> Returns True if the given Char is used as the Decimal Separator for either Currency
      /// or floating-point numbers (Single, Double, Extended) in their string representation </summary>
      function IsDecimalSeparator(const Char: System.Char): Boolean;
      /// <summary> Returns wether the given Char is valid to be used in the string representation of
      ///  Currency or floating-point numbers (Single, Double, Extended) </summary>
      function IsCurrencyChar(const Char: System.Char; const AllowNegative: Boolean = True): Boolean;
      /// <summary> Returns the Character that is used as decimal separator in the string
      /// representation of Currency Values </summary>
      property CurrencyDecimalSeparator: System.Char read GetCurrencyDecimalSeparator;
    end;
{$ENDREGION}
  strict private type
{$REGION 'Locale.TDefaultLocale'}
    TDefaultLocale = class(TInterfacedObject, ILocaleSettings)
    strict private const
      DecimalSeparators = [',', '.'];
      Digits = ['0' .. '9'];
      Others = [#8];
      Negative = '-';
      Space = ' ';
      AllowedDigitsChars = Digits;
      AllowedCurrencyChars = DecimalSeparators + AllowedDigitsChars + Others;
    strict private
{$REGION 'ILocaleSettings'}
      function IsIntegerChar(const Char: System.Char; const AllowNegative, AllowSpace: Boolean): Boolean;
      function IsCurrencyChar(const Char: System.Char; const AllowNegative: Boolean): Boolean;
      function GetCurrencyDecimalSeparator: System.Char;
      function IsDecimalSeparator(const Char: System.Char): Boolean;
{$ENDREGION}
    end;
{$ENDREGION}
  strict private
    class var FCurrent: Locale.ILocaleSettings;
    class function CreateDefault: Locale.ILocaleSettings; static;
    class function GetCurrent: Locale.ILocaleSettings; static;
    class function GetCurrencyDecimalSeparator: System.Char; static;
    class procedure SetCurrent(const Value: Locale.ILocaleSettings); static;
  public
    /// <summary> Returns wether the given Char is valid to be used in the string representation of
    /// Integer numbers </summary>
    /// <param name="AllowNegative"> Allows a '-' character to be treated as valid </param>
    /// <param name="AllowSpace"> Allows a ' ' character to be treated as valid </param>
    class function IsIntegerChar(
      const Char: System.Char;
      const AllowNegative: Boolean = True;
      const AllowSpace: Boolean = True): Boolean; static;
    /// <summary> Returns wether the given Char is valid to be used in the string representation of
    /// Currency or floating-point numbers (Single, Double, Extended) </summary>
    class function IsCurrencyChar(const Char: System.Char; const AllowNegative: Boolean = True): Boolean; static;
    /// <summary> Returns True if the given Char is used as the Decimal Separator for either Currency
    /// or floating-point numbers (Single, Double, Extended) in their string representation </summary>
    class function IsDecimalSeparator(const Char: System.Char): Boolean; static;
    /// <summary> Returns the Character that is used as decimal separator in the string representation
    /// of Currency Values </summary>
    class property CurrencyDecimalSeparator: System.Char read GetCurrencyDecimalSeparator;
    /// <summary> The System's Current implementation of the Locale Settings </summary>
    class property Current: Locale.ILocaleSettings read GetCurrent write SetCurrent;
  end;
{$ENDREGION}
 
implementation
 
uses
  System.Guard,
  System.SysUtils;
 
{$REGION 'Locale'}
 
class function Locale.CreateDefault: Locale.ILocaleSettings;
begin
  Result := Locale.TDefaultLocale.Create;
end;
 
class function Locale.GetCurrencyDecimalSeparator: System.Char;
begin
  Result := Locale.Current.CurrencyDecimalSeparator;
end;
 
class function Locale.GetCurrent: Locale.ILocaleSettings;
begin
  if not System.Assigned(Locale.FCurrent) then
    Locale.FCurrent := Locale.CreateDefault;
  Result := Locale.FCurrent;
end;
 
class function Locale.IsIntegerChar(const Char: System.Char; const AllowNegative, AllowSpace: Boolean): Boolean;
begin
  Result := Locale.Current.IsIntegerChar(Char, AllowNegative, AllowSpace);
end;
 
class function Locale.IsCurrencyChar(const Char: System.Char; const AllowNegative: Boolean): Boolean;
begin
  Result := Locale.Current.IsCurrencyChar(Char, AllowNegative);
end;
 
class function Locale.IsDecimalSeparator(const Char: System.Char): Boolean;
begin
  Result := Locale.Current.IsDecimalSeparator(Char);
end;
 
class procedure Locale.SetCurrent(const Value: Locale.ILocaleSettings);
begin
  Locale.FCurrent := Guard.CheckNotNull(Value);
end;
 
{$REGION 'Locale.TDefaultLocale'}
 
function Locale.TDefaultLocale.GetCurrencyDecimalSeparator: System.Char;
begin
  Result := '.';
end;
 
function Locale.TDefaultLocale.IsIntegerChar(
  const Char: System.Char; 
  const AllowNegative, AllowSpace: Boolean): Boolean;
var
  CharSet: TSysCharSet;
begin
  CharSet := AllowedDigitsChars;
  if AllowNegative then
    System.Include(CharSet, Negative);
 
  if AllowSpace then
    System.Include(CharSet, Space);
 
  Result := System.SysUtils.CharInSet(Char, CharSet);
end;
 
function Locale.TDefaultLocale.IsCurrencyChar(const Char: System.Char; const AllowNegative: Boolean): Boolean;
var
  CharSet: TSysCharSet;
begin
  CharSet := AllowedCurrencyChars;
  if AllowNegative then
    System.Include(CharSet, Negative);
 
  Result := System.SysUtils.CharInSet(Char, CharSet);
end;
 
function Locale.TDefaultLocale.IsDecimalSeparator(const Char: System.Char): Boolean;
begin
  Result := System.SysUtils.CharInSet(Char, DecimalSeparators);
end;
 
{$ENDREGION}
 
{$ENDREGION}
 
end.