Explorar o código

* API to collect locale settings from browser

Michaël Van Canneyt hai 1 semana
pai
achega
831e6c6d75

+ 57 - 0
packages/wasm-utils/demo/locale/localedemo.lpi

@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<CONFIG>
+  <ProjectOptions>
+    <Version Value="12"/>
+    <General>
+      <Flags>
+        <MainUnitHasCreateFormStatements Value="False"/>
+        <MainUnitHasTitleStatement Value="False"/>
+        <MainUnitHasScaledStatement Value="False"/>
+      </Flags>
+      <SessionStorage Value="InProjectDir"/>
+      <Title Value="localedemo"/>
+      <UseAppBundle Value="False"/>
+      <ResourceType Value="res"/>
+    </General>
+    <BuildModes>
+      <Item Name="Default" Default="True"/>
+    </BuildModes>
+    <PublishOptions>
+      <Version Value="2"/>
+      <UseFileFilters Value="True"/>
+    </PublishOptions>
+    <RunParams>
+      <FormatVersion Value="2"/>
+    </RunParams>
+    <Units>
+      <Unit>
+        <Filename Value="localedemo.lpr"/>
+        <IsPartOfProject Value="True"/>
+      </Unit>
+    </Units>
+  </ProjectOptions>
+  <CompilerOptions>
+    <Version Value="11"/>
+    <Target>
+      <Filename Value="localedemo.wasm"/>
+    </Target>
+    <SearchPaths>
+      <IncludeFiles Value="$(ProjOutDir)"/>
+      <OtherUnitFiles Value="../../src"/>
+      <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/>
+    </SearchPaths>
+  </CompilerOptions>
+  <Debugging>
+    <Exceptions>
+      <Item>
+        <Name Value="EAbort"/>
+      </Item>
+      <Item>
+        <Name Value="ECodetoolError"/>
+      </Item>
+      <Item>
+        <Name Value="EFOpenError"/>
+      </Item>
+    </Exceptions>
+  </Debugging>
+</CONFIG>

+ 71 - 0
packages/wasm-utils/demo/locale/localedemo.lpr

@@ -0,0 +1,71 @@
+program localedemo;
+
+uses sysutils, dateutils,wasm.locale.objects, wasiutil;
+
+var
+  I : integer;
+
+procedure testwasiutil;
+
+var
+  epoch,epoch2 : int64;
+  y,m,d,h,n,s : word;
+
+begin
+  epoch:=dateutils.DateTimeToUnix(sysutils.now);
+  wasiutil.EpochToLocal(epoch,y,m,d,h,n,s);
+  epoch2:=wasiutil.localtoepoch(y,m,d,h,n,s);
+  if epoch<>epoch2 then
+    Writeln('Epoch time error: ',epoch,'<>',epoch2)
+  else
+    Writeln('Epoch time OK: ',epoch,'=',epoch2);
+end;
+
+begin
+  With TWasmHostLocale.Create do
+    try
+      Writeln('Host locale object:');
+      Writeln('-------------------');
+      for I:=1 to 7 do
+        Writeln('Day[',i,'] long: ',LongDayNames[i],', Short: ',ShortDayNames[i]);
+      Writeln;
+      for I:=1 to 12 do
+        Writeln('Month[',i,'] long: ',LongMonthNames[i],', Short: ',ShortMonthNames[i]);
+      Writeln;
+      Writeln('Date separator   : ',DateSeparator);
+      Writeln('Time separator   : ',TimeSeparator);
+      Writeln('Decimal separator   : ',DecimalSeparator);
+      Writeln('Thousands separator : ',ThousandSeparator);
+      Writeln('Currency symbol     : ',CurrencyString);
+      Writeln('TimezoneOffsetl     : ',TimeZoneOffset);
+      TransferToFormatSettings;
+      wasiutil.UTCTimeOffset:=-TimeZoneOffset;
+    finally
+      Free;
+    end;
+  Writeln('');
+  Writeln('');
+  Writeln('Format settings:');
+  Writeln('----------------');
+  With DefaultFormatSettings do
+    begin
+    for I:=1 to 7 do
+      Writeln('Day[',i,'] long: ',LongDayNames[i],', Short: ',ShortDayNames[i]);
+    Writeln;
+    for I:=1 to 12 do
+      Writeln('Month[',i,'] long: ',LongMonthNames[i],', Short: ',ShortMonthNames[i]);
+    Writeln;
+    Writeln('Date separator   : ',DateSeparator);
+    Writeln('Time separator   : ',TimeSeparator);
+    Writeln('Decimal separator   : ',DecimalSeparator);
+    Writeln('Thousands separator : ',ThousandSeparator);
+    Writeln('Currency symbol     : ',CurrencyString);
+    end;
+  Writeln('TZ environment variable: ',GetEnvironmentVariable('TZ'));
+  Writeln('Local Timezone offset: ',GetLocalTimeOffset);
+  Writeln('Local Time: ',FormatDateTime('hh:nn:ss',Now));
+  Writeln('UTC Time: ',FormatDateTime('hh:nn:ss',NowUTC));
+  Writeln('LocalToUniversal Time: ',FormatDateTime('hh:nn:ss',DateUtils.LocalTimeToUniversal(Now)));
+  testwasiutil;
+end.
+

+ 10 - 0
packages/wasm-utils/fpmake.pp

@@ -70,6 +70,16 @@ begin
       T.Dependencies.AddUnit('wasm.websocket.api');
       T.Dependencies.AddUnit('wasm.websocket.shared');
     
+    // Locale
+    T:=P.Targets.AddUnit('wasm.locale.shared.pas');
+    
+    T:=P.Targets.AddUnit('wasm.locale.api.pas');
+      T.Dependencies.AddUnit('wasm.locale.shared');
+      
+    T:=P.Targets.AddUnit('wasm.locale.objects.pas');
+      T.Dependencies.AddUnit('wasm.locale.api');
+      T.Dependencies.AddUnit('wasm.locale.shared');
+    
     // Regexp  
     T:=P.Targets.AddUnit('wasm.regexp.shared.pas');
     

+ 71 - 0
packages/wasm-utils/src/wasm.locale.api.pas

@@ -0,0 +1,71 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 2025 by the Free Pascal development team.
+
+    WASM API calls for internationalization/localization.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+
+unit wasm.locale.api;
+
+interface
+
+
+uses
+  {$IFDEF FPC_DOTTEDUNITS}
+  System.Types,
+  {$ELSE}
+  types,
+  {$ENDIF}
+  wasm.locale.shared;
+
+function __locale_SetWasmLocale(aName: PAnsiChar; aNameLen: Longint): TLocaleError;
+                                   external modHostLocale name HostLocale_FNSetWasmLocale;
+
+
+function __locale_GetNameOfDay(aDay: Integer; 
+                               aLong: Integer; 
+                               aName: PAnsiChar; 
+                               aNameLen: PLongint): TLocaleError; 
+                                 external modHostLocale name HostLocale_FNGetNameOfDay;
+                                 
+function __locale_GetNameOfMonth(aMonth: Integer; 
+                                 aLong: Integer; 
+                                 aName: PAnsiChar; 
+                                 aNameLen: PLongint): TLocaleError; 
+                                   external modHostLocale name HostLocale_FNGetNameOfMonth;
+
+function __locale_GetDateSeparator(aSeparator: PAnsiChar;
+                                   aSeparatorLen: PLongint): TLocaleError;
+                                     external modHostLocale name HostLocale_FNGetDateSeparator;
+
+function __locale_GetTimeSeparator(aSeparator: PAnsiChar;
+                                      aSeparatorLen: PLongint): TLocaleError;
+                                        external modHostLocale name HostLocale_FNGetTimeSeparator;
+
+
+function __locale_GetDecimalSeparator(aSeparator: PAnsiChar; 
+                                      aSeparatorLen: PLongint): TLocaleError; 
+                                        external modHostLocale name HostLocale_FNGetDecimalSeparator;
+                                        
+function __locale_GetThousandSeparator(aSeparator: PAnsiChar; 
+                                       aSeparatorLen: PLongint): TLocaleError; 
+                                         external modHostLocale name HostLocale_FNGetThousandsSeparator;
+                                         
+function __locale_GetCurrencySymbol(aSymbol: PAnsiChar; 
+                                    aSymbolLen: PLongint): TLocaleError; 
+                                      external modHostLocale name HostLocale_FNGetCurrencyChar;
+                                      
+function __locale_GetTimeZoneOffset : Integer; 
+                           external modHostLocale name HostLocale_FNGetTimezoneOffset;
+
+implementation
+
+end.

+ 284 - 0
packages/wasm-utils/src/wasm.locale.objects.pas

@@ -0,0 +1,284 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 2025 by the Free Pascal development team.
+
+    WASM API object for internationalization/localization.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+
+unit wasm.locale.objects;
+
+{$mode objfpc}
+
+interface
+
+uses 
+  {$IFDEF FPC_DOTTEDUNITS}
+  System.SysUtils,
+  {$ELSE}
+  sysutils,
+  {$ENDIF} 
+  wasm.locale.shared, wasm.locale.api;
+
+Type
+  EWasmLocale = class(Exception);
+
+  { TWasmHostLocale }
+
+  TWasmHostLocale = class(TObject)
+  private
+    FCurrencySymbol: String;
+    FDateSeparator: String;
+    FDecimalSeparator: String;
+    FLongDayNames: TWeekNameArray;
+    FLongMonthNames: TMonthNameArray;
+    FShortDayNames: TWeekNameArray;
+    FShortMonthNames: TMonthNameArray;
+    FThousandSeparator: String;
+    FTimeSeparator: string;
+    FTimeZoneOffset: Integer;
+  Protected
+    function ConvertBuf(var Buf : RawByteString; aLen : integer) : string;
+    procedure InitMonths; virtual;
+    procedure InitDays; virtual;
+    procedure InitSeparators; virtual;
+    procedure InitTimeZone; virtual;
+  public
+    constructor create(aLocale : string = '');
+    procedure TransferToFormatSettings;
+    property LongMonthNames : TMonthNameArray Read FLongMonthNames;
+    property ShortMonthNames : TMonthNameArray Read FShortMonthNames;
+    property LongDayNames : TWeekNameArray Read FLongDayNames;
+    property ShortDayNames : TWeekNameArray Read FShortDayNames;
+    Property DecimalSeparator : String Read FDecimalSeparator;
+    Property ThousandSeparator : String Read FThousandSeparator;
+    Property CurrencySymbol: String Read FCurrencySymbol;
+    Property TimeZoneOffset: Integer Read FTimeZoneOffset;
+    Property DateSeparator: string Read FDateSeparator;
+    Property TimeSeparator: string Read FTimeSeparator;
+  end;
+
+implementation
+
+function TWasmHostLocale.ConvertBuf(var Buf: RawByteString; aLen: integer): string;
+begin
+  SetLength(Buf,aLen);
+  {$IF SIZEOF(char)=1}
+  Result:=Buf;
+  {$ELSE}
+  Result:=UTF8Decode(Buf);
+  {$ENDIF}
+end;
+
+procedure TWasmHostLocale.InitMonths;
+
+var
+  buf : RawByteString;
+  S : String;
+  i,res,len : longint;
+  long : boolean;
+
+  procedure initbuffer; inline;
+
+  begin
+    len:=512;
+    SetLength(buf,len);
+  end;
+
+begin
+  Buf:='';
+  InitBuffer;
+  for long in Boolean do
+    begin
+    I:=1;
+    While I<=12 do
+      begin
+      res:=__locale_GetNameOfMonth(i,ord(long),@buf[1],@len);
+      case res of
+        ELocale_SUCCESS:
+          begin
+          S:=ConvertBuf(Buf,Len);
+          if Long then
+            FLongMonthNames[i]:=S
+          else
+            FShortMonthNames[i]:=S;
+          inc(i);
+          InitBuffer;
+          end;
+        ELocale_SIZETOOSMALL:
+          SetLength(Buf,Len); // Redo the loop
+      else
+        Raise EWasmLocale.CreateFmt('Failed to get month %d name. Error code: %d',[i,res]);
+      end;
+      end;
+    end;
+
+end;
+
+procedure TWasmHostLocale.InitDays;
+var
+  buf : RawByteString;
+  S : String;
+  i,res,len : longint;
+  long : boolean;
+
+  procedure initbuffer; inline;
+
+  begin
+    len:=512;
+    SetLength(buf,len);
+  end;
+
+begin
+  Buf:='';
+  InitBuffer;
+  for long in Boolean do
+    begin
+    I:=1;
+    While (I<=7) do
+      begin
+      res:=__locale_GetNameOfDay(i,ord(long),@buf[1],@len);
+      case res of
+        ELocale_SUCCESS:
+          begin
+          S:=ConvertBuf(Buf,Len);
+          if Long then
+            FLongDayNames[i]:=S
+          else
+            FShortDayNames[i]:=S;
+          inc(i);
+          InitBuffer;
+          end;
+        ELocale_SIZETOOSMALL:
+          SetLength(Buf,Len); // Redo the loop
+      else
+        Raise EWasmLocale.CreateFmt('Failed to get day %d name. Error code: %d',[i,res]);
+      end;
+      end;
+    end;
+end;
+
+procedure TWasmHostLocale.InitSeparators;
+
+var
+  buf : RawByteString;
+  res, len : longint;
+  S : string;
+
+  procedure InitBuffer; inline;
+  begin
+    len:=24;
+    setlength(buf,len);
+  end;
+
+begin
+//  buf:='';
+
+  // Currrency
+  InitBuffer;
+  res:=__locale_GetCurrencySymbol(@Buf[1],@len);
+  if res=ELocale_SUCCESS then
+    FCurrencySymbol:=ConvertBuf(Buf,Len)
+  else
+    Raise EWasmLocale.CreateFmt('Failed to get currency symbol. Error code: %d',[res]);
+  // Browser has actually no way to determine this.
+  if FCurrencySymbol='' then
+    FCurrencySymbol:='$';
+
+  // Decimal separator
+  InitBuffer;
+  res:=__locale_GetDecimalSeparator(@Buf[1],@len);
+  if res=ELocale_SUCCESS then
+    FDecimalSeparator:=ConvertBuf(Buf,Len)
+  else
+    Raise EWasmLocale.CreateFmt('Failed to get decimal separator. Error code: %d',[res]);
+
+  // Thousands separator
+  InitBuffer;
+  res:=__locale_GetThousandSeparator(@Buf[1],@len);
+  if res=ELocale_SUCCESS then
+    FThousandSeparator:=ConvertBuf(Buf,Len)
+  else
+    Raise EWasmLocale.CreateFmt('Failed to thousands separator. Error code: %d',[res]);
+
+  // Date separator
+  InitBuffer;
+  res:=__locale_GetDateSeparator(@Buf[1],@len);
+  if res=ELocale_SUCCESS then
+    FDateSeparator:=ConvertBuf(Buf,Len)
+  else
+    Raise EWasmLocale.CreateFmt('Failed to get date separator. Error code: %d',[res]);
+  // Time separator
+  InitBuffer;
+  res:=__locale_GetTimeSeparator(@Buf[1],@len);
+  if res=ELocale_SUCCESS then
+    FTimeSeparator:=ConvertBuf(Buf,Len)
+  else
+    Raise EWasmLocale.CreateFmt('Failed to get time separator. Error code: %d',[res]);
+end;
+
+procedure TWasmHostLocale.InitTimeZone;
+begin
+  FTimeZoneOffset:=__locale_GetTimeZoneOffset;
+end;
+
+constructor TWasmHostLocale.create(aLocale: string);
+
+var
+  lLocale : AnsiString;
+
+begin
+  {$IF SIZEOF(CHAR)=1}
+  lLocale:=aLocale;
+  {$ELSE}
+  lLocale:=UTF8Encode(aLocale);
+  {$ENDIF}
+  if lLocale<>'' then
+    __locale_SetWasmLocale(PAnsiChar(lLocale),Length(lLocale));
+  InitMonths;
+  InitDays;
+  InitSeparators;
+  InitTimeZone;
+end;
+
+procedure TWasmHostLocale.TransferToFormatSettings;
+
+  function First(aString : String; aDefault : char) : char;
+  begin
+    if aString='' then
+      Result:=aDefault
+    else
+      Result:=aString[1];
+  end;
+
+var
+  I : Integer;
+
+begin
+  DefaultFormatSettings.DateSeparator:=First(FDateSeparator,'/');
+  DefaultFormatSettings.TimeSeparator:=First(FTimeSeparator,':');
+  DefaultFormatSettings.DecimalSeparator:=First(FDecimalSeparator,'.');
+  DefaultFormatSettings.CurrencyString:=FCurrencySymbol;
+  DefaultFormatSettings.ThousandSeparator:=First(FThousandSeparator,',');
+  for I:=1 to 12 do
+    begin
+    DefaultFormatSettings.LongMonthNames[i]:=LongMonthNames[i];
+    DefaultFormatSettings.ShortMonthNames[i]:=ShortMonthNames[i];
+    end;
+  for I:=1 to 7 do
+    begin
+    DefaultFormatSettings.LongDayNames[i]:=LongDayNames[i];
+    DefaultFormatSettings.ShortDayNames[i]:=ShortDayNames[i];
+    end;
+//  Sysutils.
+end;
+
+end.
+

+ 47 - 0
packages/wasm-utils/src/wasm.locale.shared.pas

@@ -0,0 +1,47 @@
+{
+    This file is part of the Free Pascal run time library.
+    Copyright (c) 2025 by the Free Pascal development team.
+
+    WASM API constants for internationalization/localization.
+
+    See the file COPYING.FPC, included in this distribution,
+    for details about the copyright.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ **********************************************************************}
+
+unit wasm.locale.shared;
+
+
+{$mode ObjFPC}
+
+interface
+
+Type
+  TLocaleError = longint;
+
+const
+  modHostLocale = 'hostlocale';
+
+  HostLocale_FNSetWasmLocale = 'hostlocale_setwasmlocale';
+  HostLocale_FNGetTimezoneOffset = 'hostlocale_gettimezoneoffset';
+  HostLocale_FNGetNameOfMonth = 'hostlocale_getnameofmonth';
+  HostLocale_FNGetNameOfDay = 'hostlocale_getnameofday';
+  HostLocale_FNGetTimeSeparator = 'hostlocale_gettimeseparator';
+  HostLocale_FNGetDateSeparator = 'hostlocale_getdateseparator';
+  HostLocale_FNGetDecimalSeparator = 'hostlocale_getdecimalseparator';
+  HostLocale_FNGetThousandsSeparator = 'hostlocale_getthousandsseparator';
+  HostLocale_FNGetCurrencyFormat = 'hostlocale_getcurrencyformat';
+  HostLocale_FNGetCurrencyChar = 'hostlocale_getcurrencyChar';
+
+  ELocale_SUCCESS = 0;
+  ELocale_INVALIDINDEX = -1;
+  ELocale_SIZETOOSMALL = -2;
+
+implementation
+
+end.
+