View Issue Details

IDProjectCategoryView StatusLast Update
0014108LazarusLCLpublic2009-10-23 00:40
ReporterAlexander Grau Assigned ToTom Gregorovic  
PrioritynormalSeverityfeatureReproducibilityalways
Status closedResolutionfixed 
Product Version0.9.26.3 (SVN) 
Fixed in Version0.9.27 (SVN) 
Summary0014108: DefaultTranslator doesn't recognize locale on Mac OS X
DescriptionThe DefaultTranslator doesn't recognize the Mac OS X preferred language (and thus the IDE always starts in English). On OS X, the preferred languages is set in the OS X Preferences by setting the order of all available (common) languages from top (preferred) to bottom (less preferred). For example, in the OS X preferences, you set "German, French, English, ... , Chinese, Japanese" if you want an OS X application to start in German if German is available, if German is not available start in French, if this not available in English and so on.

In current DefaultTranslator, "FindLocaleFileName" has no Mac specific part.

I have attached code to that can be used to implement that missing Mac-specific part. The key function is 'CFBundleCopyLocalizationsForPreferences' which returns the preferred locale (en, de, es, etc.) for a list of given locales the application supports. The returned locale is based on the Mac Preferences as described above.



TagsDefaultTranslator, Detection, FindLocaleFileName, Language, Locale
Fixed in Revision20896
LazTarget-
WidgetsetCarbon
Attached Files

Activities

2009-07-06 23:48

 

lang.pas (5,600 bytes)   
unit lang;

{$mode objfpc}{$H+}


interface

uses
{$IFDEF WIN32}
  windows,
{$ELSE IFDEF Darwin}
  MacOSAll,
{$ENDIF}
  Classes, SysUtils, FileUtil, dos, inifiles, gettext;

type
  DynArray = array of string;

  TLanguage=class
  public
    locale: string;
    liffilename: string;
    lif: tinifile;
    enlif: tinifile;
    constructor create;
    destructor destroy;
    function getPreferredLocale(list: array of string): string;
    function locateLanguageFileLocales(path: string): DynArray;
    procedure setLocale(alocale: string);
    function translate(key: string): string;
  end;

// use this function for application-wide string translation
function tr(key: string): string;


var
  language: TLanguage;


implementation


function tr(key: string): string;
begin
  result:=language.translate(key);
end;

// ---------------------------------------------------------
// locates all available language files (.lif)
function TLanguage.locateLanguageFileLocales(path: string): DynArray;
var
  filename: string;
  rec : TSearchRec;
  name: string;
  found: longint;
  i: integer;
begin
  setlength(result, 0);
  i:=0;
  found:=FindFirstUTF8(path + DirectorySeparator + '*.lif',faAnyFile,rec);
  while found = 0 do
  begin
    if (rec.Name <> '.') and (rec.Name <> '..') then
    begin
      inc(i);
      setlength(result, i);
      result[i-1]:=ChangeFileExt(ExtractFileName(rec.Name), '');
      //writeln(stderr, 'found:', result[i-1]);
    end;
    Found := FindNextUTF8(rec);
  end;
  FindCloseUTF8(rec);
end;

constructor TLanguage.create;
begin
  lif:=nil;
  //writeln(stderr, 'lang init');
  setlocale('en');
  enlif:=lif;
  lif:=nil;
  setlocale( getPreferredLocale( locateLanguageFileLocales('liclang') ) );
end;

// set locale for translation (en, de, es, jp, zh_CN, zh_TW etc.)
procedure TLanguage.setLocale(alocale: string);
var
  exefolder: string;
  list: array of string;
  i: integer;
begin
  exefolder:=ExtractFilePath(ParamStr(0));
  {$IFDEF Darwin}
    exefolder:=GetCurrentDir + '/';
  {$ENDIF}
  locale:=alocale;
  liffilename:=exefolder + 'liclang' + DirectorySeparator + locale + '.lif';
  if (FileExists(liffilename)) then
  begin
    //writeln(stderr, 'locale: ', locale);
    //writeln(stderr, 'lif: ', liffilename);
    if (lif <> nil) then lif.free;
    lif:=tinifile.create(liffilename);
    //writeln('gui_OK=>', translate('gui_OK'));
  end else
  begin
    // try first two letters only
    list:=locateLanguageFileLocales('liclang');
    for i:=low(list) to high(list) do
    begin
      if leftstr(lowercase(list[i]), 2) = leftstr(lowercase(locale), 2) then
      begin
        setlocale(list[i]);
        break;
      end;
    end;
  end;
end;

destructor TLanguage.destroy;
begin
  lif.Free;
  enlif.free;
end;

// translate a string
function TLanguage.translate(key: string): string;
var
  value: string;
begin
  value:=lif.ReadString('main', key, '');
  if (value = '') then value:=enlif.ReadString('main', key, '');
  if (value = '') then value:=key;
  result:=value;
end;


{$IFDEF Windows}

// for a list of locales (for example:  en, de, es, zh_TW, zh_CN, etc.) returns the preferred locale (=user locale)
// returns 'en' if no locale matches the preferred user locale
function TLanguage.getPreferredLocale(list: array of string): string;
var
  i: integer;
  lang: string;
  fallbacklang: string;
begin
  GetLanguageIDs(Lang,FallbackLang); // in unit gettext
  //writeln(stderr, 'lang=',lang);
  //writeln(stderr, 'fallbacklang=',fallbacklang);
  result:='en';
  // try ISO code exact match first (higher priority)
  for i:=low(list) to high(list) do
  begin
    if lowercase(list[i]) = lowercase(lang) then
    begin
      result:=lowercase(lang);
      exit;
    end;
  end;
  (*if pos('_', lang) > 0 then
  begin
    lang:=copy(lang, 1, 2);
    // there was no exact match, try first two letter codes only (e.g. de_DE => de)
    for i:=low(list) to high(list) do
    begin
      if list[i] = lang then
      begin
        result:=lang;
        exit;
      end;
    end;
  end;*)
  // there was no exact match, try fallback language
  for i:=low(list) to high(list) do
  begin
    if lowercase(list[i]) = lowercase(fallbacklang) then
    begin
      result:=lowercase(fallbacklang);
      exit;
    end;
  end;
end;

{$ELSE IFDEF Darwin}

// for a list of locales (for example:  en, de, es, zh_TW, zh_CN, etc.) returns the preferred locale (=user locale)
// returns 'en' if no locale matches the preferred user locale
function TLanguage.getPreferredLocale(list: array of string): string;
var
  code: UTF8String;
  ref: CFStringRef;
  arrRef: CFArrayRef;
  locArray: CFMutableArrayRef;
  i: integer;
begin
  locArray:=CFArrayCreateMutable( kCFAllocatorDefault ,
    length(list), @kCFTypeArrayCallBacks ) ;

  for i:=low(list) to high(list) do
    CFArrayAppendValue( locArray , CFStringCreateWithCString(nil, pchar(list[i]), kCFStringEncodingUTF8)) ;

  arrRef := CFBundleCopyLocalizationsForPreferences( locArray, nil);
  if CFArrayGetCount(arrRef) > 0 then
  begin
    ref:=CFArrayGetValueAtIndex(arrRef, 0);
    SetLength(code, 512);
    CFStringGetCString(ref, @code[1], Length(code), kCFStringEncodingUTF8);
    SetLength(code, StrLen(PChar(@code[1])));
    result:=code;
  end else result:='en';
  CFRelease(locArray);
end;

{$ENDIF}

initialization
  language:=tlanguage.create;

finalization
  language.free;

end.

lang.pas (5,600 bytes)   

Tom Gregorovic

2009-07-19 14:23

developer   ~0029175

Thanks, I made only one change that now it uses languages listed in application bundle property list in CFBundleLocalizations key, see lazarus.app/Contents/Info.plist for example.

Issue History

Date Modified Username Field Change
2009-07-06 23:48 Alexander Grau New Issue
2009-07-06 23:48 Alexander Grau File Added: lang.pas
2009-07-06 23:48 Alexander Grau Widgetset => Carbon
2009-07-06 23:52 Alexander Grau Tag Attached: DefaultTranslator
2009-07-06 23:52 Alexander Grau Tag Attached: Detection
2009-07-06 23:52 Alexander Grau Tag Attached: FindLocaleFileName
2009-07-06 23:52 Alexander Grau Tag Attached: Language
2009-07-06 23:52 Alexander Grau Tag Attached: Locale
2009-07-07 07:24 Vincent Snijders LazTarget => -
2009-07-07 07:24 Vincent Snijders Status new => acknowledged
2009-07-19 14:07 Tom Gregorovic Status acknowledged => assigned
2009-07-19 14:07 Tom Gregorovic Assigned To => Tom Gregorovic
2009-07-19 14:23 Tom Gregorovic Fixed in Revision => 20896
2009-07-19 14:23 Tom Gregorovic Status assigned => resolved
2009-07-19 14:23 Tom Gregorovic Fixed in Version => 0.9.27 (SVN)
2009-07-19 14:23 Tom Gregorovic Resolution open => fixed
2009-07-19 14:23 Tom Gregorovic Note Added: 0029175
2009-10-23 00:40 Marc Weustink Status resolved => closed