Selaa lähdekoodia

Re-implementing language autodetection for the "Translations" subfolder + Unless user customizes language, automatically using the best language match each run + Including all translation (30% at least) in installation, but automatically using only those that are complete (80% at least) + Marking the default, incomplete and invalid languages in Preferences dialog + Using applied language, instead of configured language in URLs and counters

Source commit: ad428b3c09a9e0081b84b8090100d62d1edf39f4
Martin Prikryl 9 vuotta sitten
vanhempi
sitoutus
4c3afd30d8

+ 1 - 4
deployment/winscp.isl

@@ -34,13 +34,10 @@ SetupTypeTitle=Setup Type
 SetupTypePrompt=What type of setup do you want?
 TypicalType=&Typical installation
 TypicalType1=- installs to default destination
-TypicalType2Intl=- installs all components, but only %1 translation
-TypicalType2Eng=- installs all components, but no translations
+TypicalType2=- installs all components
 TypicalType3=- enables most typical features
 TypicalUpgradeType=&Full upgrade
 TypicalUpgradeType1=- upgrades all previously installed components
-TypicalUpgradeTypeNoTransl=- except for all translations, as this release does NOT include any
-TypicalUpgradeTypeMissingTransl=- except for some translations that are NOT included in this release: %1
 CustomType=&Custom installation
 CustomType1=- allows full selection of destination, components and features
 CustomUpgradeType=&Custom upgrade or new installation

+ 13 - 116
deployment/winscpsetup.iss

@@ -19,6 +19,12 @@
   #define CompletenessThreshold Int(CompletenessThreshold)
 #endif
 
+#ifndef InclusionThreshold
+  #define InclusionThreshold 100
+#else
+  #define InclusionThreshold Int(InclusionThreshold)
+#endif
+
 #ifndef PuttySourceDir
   #if DirExists("c:\Program Files (x86)\PuTTY")
     #define PuttySourceDir "c:\Program Files (x86)\PuTTY"
@@ -152,12 +158,14 @@ Name: {#DefaultLang}; MessagesFile: {#MessagesPath(DefaultLang)}
   #define LangCompleteness Int(ReadIni(MessagesPath(Lang), "CustomOptions", "TranslationCompleteness"))
 
   #expr Languages[LanguageCount*4] = Lang
+  ; Not used atm
   #expr Languages[LanguageCount*4+1] = LangName
+  ; Not used atm
   #expr Languages[LanguageCount*4+2] = LangID
   #expr Languages[LanguageCount*4+3] = LangCompleteness
   #expr LanguageCount++
 
-#if LangCompleteness > CompletenessThreshold
+#if LangCompleteness >= CompletenessThreshold
 Name: {#Lang}; MessagesFile: {#MessagesPath(Lang)}
   #expr AnyLanguageComplete = 1
 #endif
@@ -323,33 +331,13 @@ Root: HKLM; SubKey: "{#RegistryKey}"; \
   ValueType: dword; ValueName: "DefaultCollectUsage"; ValueData: 1; \
   Tasks: enableupdates\enablecollectusage; Flags: noerror
 
-#if AnyLanguageComplete == 1
-
-[Components]
-Name: transl\eng; Description: {#EnglishLang}; Types: full custom compact; \
-  Flags: fixed
-
-#endif
-
 #sub EmitLang
 
-  #if Languages[LangI*4+3] > CompletenessThreshold
-
-[Components]
-Name: transl\{#Languages[LangI*4]}; Description: {#Languages[LangI*4+1]}; \
-  Types: full compact custom; Check: IsLang('{#Languages[LangI*4]}')
-Name: transl\{#Languages[LangI*4]}; Description: {#Languages[LangI*4+1]}; \
-  Check: not IsLang('{#Languages[LangI*4]}')
+  #if Languages[LangI*4+3] >= InclusionThreshold
 
 [Files]
 Source: "{#TranslationDir}\WinSCP.{#Languages[LangI*4]}"; DestDir: "{app}\Translations"; \
-  Components: transl\{#Languages[LangI*4]}; Flags: ignoreversion
-
-[Registry]
-; set program default language to setup language, but only if user installs it
-Root: HKCU; SubKey: "{#RegistryKey}\Configuration\Interface"; \
-  ValueType: dword; ValueName: "LocaleSafe"; ValueData: {#Languages[LangI*4+2]}; \
-  Components: transl\{#Languages[LangI*4]}; Languages: {#Languages[LangI*4]}
+  Components: transl; Flags: ignoreversion
 
   #endif
 
@@ -394,7 +382,6 @@ var
   AreUpdatesEnabled: Boolean;
   AutomaticUpdate: Boolean;
   Upgrade: Boolean;
-  MissingTranslations: string;
   PrevVersion: string;
   ShellExtNewerCacheFileName: string;
   ShellExtNewerCacheResult: Boolean;
@@ -418,11 +405,6 @@ begin
   MsgBox(Text, mbInformation, MB_OK);
 end;
 
-function IsLang(Lang: string): Boolean;
-begin
-  Result := (Lang = ActiveLanguage);
-end;
-
 function IsWinVista: Boolean;
 begin
   Result := (GetWindowsVersion >= $06000000);
@@ -545,32 +527,6 @@ begin
   end;
 end;
 
-function LanguageName(Lang: string; Unknown: string): string;
-begin
-  #sub EmitLang2
-  if Lang = '{#Languages[LangI*4]}' then Result := '{#Languages[LangI*4+1]}'
-    else
-  #endsub /* sub EmitLang2 */
-
-  #for {LangI = 0; LangI < LanguageCount; LangI++} EmitLang2
-
-  Result := Unknown;
-end;
-
-function ContainsLanguage(Lang: string): Boolean;
-begin
-  #sub EmitLang3
-    #if Languages[LangI*4+3] > CompletenessThreshold
-  if (Lang = '{#Languages[LangI*4]}') then Result := True
-    else
-    #endif
-  #endsub /* sub EmitLang3 */
-
-  #for {LangI = 0; LangI < LanguageCount; LangI++} EmitLang3
-
-  Result := False;
-end;
-
 function LanguageCompleteness(Lang: string): Integer;
 begin
   #sub EmitLang4
@@ -658,48 +614,6 @@ begin
   WizardForm.ActiveControl := TWinControl(TControl(Sender).Tag);
 end;
 
-type
-  TProcessTranslationEvent = procedure(Lang: string; FileName: string);
-
-procedure CollectNames(Lang: string; FileName: string);
-begin
-  if Length(MissingTranslations) > 0 then
-    MissingTranslations := MissingTranslations + ', ';
-  MissingTranslations := MissingTranslations + LanguageName(Lang, Lang);
-end;
-
-procedure ProcessMissingTranslations(OnProcessTranslation: TProcessTranslationEvent);
-var
-  Path: string;
-  FindRec: TFindRec;
-  Ext: string;
-  LExt: string;
-begin
-  Path := AddBackslash(WizardDirValue);
-
-  if FindFirst(Path + '{#TranslationFileMask}', FindRec) then
-  begin
-    try
-      repeat
-        if FindRec.Attributes and FILE_ATTRIBUTE_DIRECTORY = 0 then
-        begin
-          Ext := Uppercase(ExtractFileExt(FindRec.Name));
-          if Pos('.', Ext) = 1  then
-          begin
-            Ext := Uppercase(Copy(Ext, 2, Length(Ext) - 1));
-            LExt := Lowercase(Ext);
-            if (Pos('-' + Ext + '-', '-{#AllTranslations}-') > 0) and
-               not ContainsLanguage(LExt) then
-              OnProcessTranslation(LExt, Path + FindRec.Name);
-          end;
-        end;
-      until not FindNext(FindRec);
-    finally
-      FindClose(FindRec);
-    end;
-  end;
-end;
-
 function WillRestart: Boolean;
 begin
   Result := WizardForm.YesRadio.Visible and WizardForm.YesRadio.Checked;
@@ -991,8 +905,6 @@ begin
     ShowMessage(FmtMessage(CustomMessage('IncompleteTranslation'), [IntToStr(Completeness)]));
   end;
 
-  ProcessMissingTranslations(@CollectNames);
-
   WizardForm.KeyPreview := True;
   WizardForm.OnKeyDown := @FormKeyDown;
   // to accomodate one more task
@@ -1087,30 +999,15 @@ begin
   Caption.WordWrap := True;
   if not Upgrade then
   begin
-    if DefaultLang then
-      S := CustomMessage('TypicalType2Eng')
-    else
-      S := FmtMessage(CustomMessage('TypicalType2Intl'), [CustomMessage('LocalLanguageName')]);
     Caption.Caption :=
       CustomMessage('TypicalType1') + NewLine +
-      S + NewLine +
+      CustomMessage('TypicalType2') + NewLine +
       CustomMessage('TypicalType3');
   end
     else
   begin
-    if Length(MissingTranslations) > 0 then
-    begin
-      #if AnyLanguageComplete
-        S := FmtMessage(CustomMessage('TypicalUpgradeTypeMissingTransl'), [MissingTranslations]);
-      #else
-        S := CustomMessage('TypicalUpgradeTypeNoTransl');
-      #endif
-      S := NewLine + S;
-    end
-      else S := '';
-
     Caption.Caption :=
-      CustomMessage('TypicalUpgradeType1') + S;
+      CustomMessage('TypicalUpgradeType1');
   end;
   Caption.Left := ScaleX(4) + ScaleX(20);
   Caption.Width := SetupTypePage.SurfaceWidth - Caption.Left;

+ 15 - 5
source/core/Common.cpp

@@ -2251,19 +2251,29 @@ TLibModule * __fastcall FindModule(void * Instance)
   return CurModule;
 }
 //---------------------------------------------------------------------------
-UnicodeString __fastcall LoadStr(int Ident, unsigned int MaxLength)
+static UnicodeString __fastcall DoLoadStrFrom(HINSTANCE Module, int Ident, unsigned int MaxLength)
 {
-  TLibModule * MainModule = FindModule(HInstance);
-  DebugAssert(MainModule != NULL);
-
   UnicodeString Result;
   Result.SetLength(MaxLength);
-  int Length = LoadString((HINSTANCE)MainModule->ResInstance, Ident, Result.c_str(), MaxLength);
+  int Length = LoadString(Module, Ident, Result.c_str(), MaxLength);
   Result.SetLength(Length);
 
   return Result;
 }
 //---------------------------------------------------------------------------
+UnicodeString __fastcall LoadStrFrom(HINSTANCE Module, int Ident)
+{
+  // 1024 = what VCL LoadStr limits the string to
+  return DoLoadStrFrom(Module, Ident, 1024);
+}
+//---------------------------------------------------------------------------
+UnicodeString __fastcall LoadStr(int Ident, unsigned int MaxLength)
+{
+  TLibModule * MainModule = FindModule(HInstance);
+  DebugAssert(MainModule != NULL);
+  return DoLoadStrFrom((HINSTANCE)MainModule->ResInstance, Ident, MaxLength);
+}
+//---------------------------------------------------------------------------
 UnicodeString __fastcall LoadStrPart(int Ident, int Part)
 {
   UnicodeString Result;

+ 1 - 0
source/core/Common.h

@@ -111,6 +111,7 @@ unsigned int __fastcall CancelAnswer(unsigned int Answers);
 unsigned int __fastcall AbortAnswer(unsigned int Answers);
 unsigned int __fastcall ContinueAnswer(unsigned int Answers);
 UnicodeString __fastcall LoadStr(int Ident, unsigned int MaxLength);
+UnicodeString __fastcall LoadStrFrom(HINSTANCE Module, int Ident);
 UnicodeString __fastcall LoadStrPart(int Ident, int Part);
 UnicodeString __fastcall EscapeHotkey(const UnicodeString & Caption);
 bool __fastcall CutToken(UnicodeString & Str, UnicodeString & Token,

+ 1 - 1
source/forms/About.cpp

@@ -222,7 +222,7 @@ void __fastcall TAboutDialog::LoadThirdParty()
 
     AddPara(ThirdParty,
       TranslationHeader + Br +
-      FMTLOAD(ABOUT_TRANSLATIONS_COPYRIGHT, (GUIConfiguration->LocaleCopyright())) + Br +
+      FMTLOAD(ABOUT_TRANSLATIONS_COPYRIGHT, (GUIConfiguration->AppliedLocaleCopyright())) + Br +
       (!TranslatorInfo.IsEmpty() ? TranslatorInfo + Br : UnicodeString()) +
       (!TranslatorUrl.IsEmpty() ? CreateLink(TranslatorUrl) : UnicodeString()));
   }

+ 1 - 1
source/forms/MessageDlg.cpp

@@ -161,7 +161,7 @@ void __fastcall TMessageForm::ReportButtonClick(TObject * /*Sender*/)
   // And we need to preserve the other parameters.
   UnicodeString Url =
     FMTLOAD(ERROR_REPORT_URL2,
-      (Configuration->ProductVersion, GUIConfiguration->LocaleHex,
+      (Configuration->ProductVersion, GUIConfiguration->AppliedLocaleHex,
        EncodeUrlString(GetReportText())));
 
   OpenBrowser(Url);

+ 46 - 8
source/forms/Preferences.cpp

@@ -165,14 +165,21 @@ void __fastcall TPreferencesDialog::LoadLanguages()
   {
     LanguagesView->Items->Clear();
 
-    TStrings * Locales = GUIConfiguration->Locales;
+    LCID Locale = GUIConfiguration->Locale;
+    if (Locale == NULL)
+    {
+      DebugAssert(GUIConfiguration->AppliedLocale == WinConfiguration->DefaultLocale);
+      Locale = WinConfiguration->DefaultLocale;
+    }
+
+    TObjectList * Locales = GUIConfiguration->Locales;
     for (int Index = 0; Index < Locales->Count; Index++)
     {
       TListItem * Item = LanguagesView->Items->Add();
-      Item->Caption = Locales->Strings[Index];
-      Item->Data = Locales->Objects[Index];
-      Item->Focused =
-        (reinterpret_cast<LCID>(Locales->Objects[Index]) == GUIConfiguration->Locale);
+      TLocaleInfo * LocaleInfo = DebugNotNull(dynamic_cast<TLocaleInfo *>(Locales->Items[Index]));
+      Item->Caption = LocaleInfo->Name;
+      Item->Data = LocaleInfo;
+      Item->Focused = (LocaleInfo->Locale == Locale);
       Item->Selected = Item->Focused;
     }
 
@@ -864,8 +871,20 @@ void __fastcall TPreferencesDialog::SaveConfiguration()
     // languages
     if (LanguagesView->ItemFocused != NULL)
     {
-      GUIConfiguration->Locale =
-        reinterpret_cast<LCID>(LanguagesView->ItemFocused->Data);
+      TLocaleInfo * LocaleInfo = static_cast<TLocaleInfo *>(LanguagesView->ItemFocused->Data);
+      LCID Locale;
+      // Do not change the locale settings, unless changed explicitly by user
+      // to allow an automatic upgrade to new translation once the UI language translation
+      // becomes available
+      if (LocaleInfo->Locale == WinConfiguration->DefaultLocale)
+      {
+        Locale = NULL;
+      }
+      else
+      {
+        Locale = LocaleInfo->Locale;
+      }
+      GUIConfiguration->Locale = Locale;
     }
 
     // This possibly fails, make it last, so that the other settings are preserved.
@@ -1221,7 +1240,7 @@ void __fastcall TPreferencesDialog::UpdateControls()
     LanguageChangeLabel->Visible =
       DebugAlwaysTrue(!GUIConfiguration->CanApplyLocaleImmediately) &&
       (LanguagesView->ItemFocused != NULL) &&
-      (reinterpret_cast<LCID>(LanguagesView->ItemFocused->Data) != GUIConfiguration->AppliedLocale);
+      (static_cast<TLocaleInfo *>(LanguagesView->ItemFocused->Data)->Locale != GUIConfiguration->AppliedLocale);
 
     // logging
     EnableControl(LogProtocolCombo, EnableLoggingCheck->Checked);
@@ -2661,3 +2680,22 @@ void __fastcall TPreferencesDialog::ConfigureCommand()
   UpdateCustomCommandsView();
 }
 //---------------------------------------------------------------------------
+void __fastcall TPreferencesDialog::LanguagesViewCustomDrawItem(
+  TCustomListView * Sender, TListItem * Item, TCustomDrawState /*State*/, bool & /*DefaultDraw*/)
+{
+  TLocaleInfo * LocaleInfo = static_cast<TLocaleInfo *>(Item->Data);
+  if (LocaleInfo->Locale == WinConfiguration->DefaultLocale)
+  {
+    Sender->Canvas->Font->Style = Sender->Canvas->Font->Style << fsBold;
+  }
+
+  if (LocaleInfo->Completeness < 0)
+  {
+    Sender->Canvas->Font->Color = clRed;
+  }
+  else if (LocaleInfo->Completeness < 100)
+  {
+    Sender->Canvas->Font->Color = clGrayText;
+  }
+}
+//---------------------------------------------------------------------------

+ 1 - 0
source/forms/Preferences.dfm

@@ -2817,6 +2817,7 @@ object PreferencesDialog: TPreferencesDialog
             ShowColumnHeaders = False
             TabOrder = 0
             ViewStyle = vsReport
+            OnCustomDrawItem = LanguagesViewCustomDrawItem
             OnSelectItem = ListViewSelectItem
           end
           object LanguagesGetMoreButton: TButton

+ 1 - 0
source/forms/Preferences.h

@@ -397,6 +397,7 @@ __published:
   void __fastcall CustomCommandsViewMouseMove(TObject *Sender, TShiftState Shift, int X, int Y);
   void __fastcall BackgroundConfirmationsLinkClick(TObject *Sender);
   void __fastcall ConfigureCommandButtonClick(TObject *Sender);
+  void __fastcall LanguagesViewCustomDrawItem(TCustomListView * Sender, TListItem * Item, TCustomDrawState State, bool & DefaultDraw);
 
 private:
   TPreferencesMode FPreferencesMode;

+ 181 - 116
source/windows/GUIConfiguration.cpp

@@ -522,8 +522,9 @@ bool __fastcall TCopyParamList::GetAnyRule() const
 //---------------------------------------------------------------------------
 __fastcall TGUIConfiguration::TGUIConfiguration(): TConfiguration()
 {
-  SetInitialLocale(0);
-  FLocales = CreateSortedStringList();
+  FLocale = 0;
+  SetAppliedLocale(InternalLocale(), UnicodeString());
+  FLocales = new TObjectList();
   FLastLocalesExts = L"*";
   FCopyParamList = new TCopyParamList();
   CoreSetResourceModule(GetResourceModule());
@@ -813,20 +814,29 @@ HINSTANCE __fastcall TGUIConfiguration::LoadNewResourceModule(LCID ALocale,
     // Look for a potential language/country translation
     UnicodeString ModulePath = GetTranslationModule(Module);
     NewInstance = LoadLibraryEx(ModulePath.c_str(), 0, LOAD_LIBRARY_AS_DATAFILE);
-    if (!NewInstance)
+    if (NewInstance)
     {
-      // Finally look for a language only translation
-      Module.SetLength(Module.Length() - 1);
-      ModulePath = GetTranslationModule(Module);
-      NewInstance = LoadLibraryEx(ModulePath.c_str(), 0, LOAD_LIBRARY_AS_DATAFILE);
-      if (NewInstance)
-      {
-        LibraryFileName = ModulePath;
-      }
+      LibraryFileName = ModulePath;
     }
     else
     {
-      LibraryFileName = ModulePath;
+      DWORD PrimaryLang = PRIMARYLANGID(ALocale);
+      DWORD SubLang = SUBLANGID(ALocale);
+      DebugAssert(SUBLANG_DEFAULT == SUBLANG_CHINESE_TRADITIONAL);
+      // Finally look for a language-only translation.
+      // But for Chinese, never use "traditional" (what is the "default" Chinese), if we want "Simplified"
+      // (the same what Inno Setup does)
+      if ((PrimaryLang != LANG_CHINESE) ||
+          (SubLang == SUBLANG_CHINESE_TRADITIONAL))
+      {
+        Module.SetLength(Module.Length() - 1);
+        ModulePath = GetTranslationModule(Module);
+        NewInstance = LoadLibraryEx(ModulePath.c_str(), 0, LOAD_LIBRARY_AS_DATAFILE);
+        if (NewInstance)
+        {
+          LibraryFileName = ModulePath;
+        }
+      }
     }
   }
 
@@ -866,62 +876,85 @@ LCID __fastcall TGUIConfiguration::InternalLocale()
 //---------------------------------------------------------------------------
 LCID __fastcall TGUIConfiguration::GetLocale()
 {
-  if (!FLocale)
-  {
-    SetInitialLocale(InternalLocale());
-  }
   return FLocale;
 }
 //---------------------------------------------------------------------------
 void __fastcall TGUIConfiguration::SetLocale(LCID value)
 {
-  SetLocaleInternal(value, false);
+  if (Locale != value)
+  {
+    SetLocaleInternal(value, false, false);
+  }
 }
 //---------------------------------------------------------------------------
 void __fastcall TGUIConfiguration::SetLocaleSafe(LCID value)
 {
-  SetLocaleInternal(value, true);
+  if (Locale != value)
+  {
+    SetLocaleInternal(value, true, false);
+  }
 }
 //---------------------------------------------------------------------------
-UnicodeString __fastcall TGUIConfiguration::GetLocaleHex()
+UnicodeString __fastcall TGUIConfiguration::GetAppliedLocaleHex()
 {
-  return IntToHex(__int64(GUIConfiguration->Locale), 4);
+  return IntToHex(__int64(AppliedLocale), 4);
 }
 //---------------------------------------------------------------------------
-void __fastcall TGUIConfiguration::SetLocaleInternal(LCID value, bool Safe)
+int __fastcall TGUIConfiguration::GetResourceModuleCompleteness(HINSTANCE /*Module*/)
 {
-  if (Locale != value)
+  return 100;
+}
+//---------------------------------------------------------------------------
+bool __fastcall TGUIConfiguration::IsTranslationComplete(HINSTANCE /*Module*/)
+{
+  return true;
+}
+//---------------------------------------------------------------------------
+void __fastcall TGUIConfiguration::SetLocaleInternal(LCID value, bool Safe, bool CompleteOnly)
+{
+  LCID L = value;
+  if (L == NULL)
   {
-    HINSTANCE Module;
-    UnicodeString FileName;
+    L = GetUserDefaultUILanguage();
+  }
 
-    try
+  HINSTANCE Module = NULL;
+  UnicodeString FileName;
+
+  try
+  {
+    Module = LoadNewResourceModule(L, FileName);
+    DebugAssert(Module != NULL);
+    if (CompleteOnly && !IsTranslationComplete(Module))
     {
-      Module = LoadNewResourceModule(value, FileName);
-      DebugAssert(Module != NULL);
+      Abort();
     }
-    catch(...)
+  }
+  catch (...)
+  {
+    if (Module != NULL)
     {
-      if (Safe)
-      {
-        // ignore any exception while loading locale
-        Module = NULL;
-      }
-      else
-      {
-        throw;
-      }
+      FreeResourceModule(Module);
+      Module = NULL;
     }
 
-    if (Module != NULL)
+    if (Safe)
     {
-      FLocale = value;
-      if (CanApplyLocaleImmediately)
-      {
-        FAppliedLocale = value;
-        SetResourceModule(Module);
-        FLocaleModuleName = FileName;
-      }
+      // ignore any exception while loading locale
+    }
+    else
+    {
+      throw;
+    }
+  }
+
+  if (Module != NULL)
+  {
+    FLocale = value;
+    if (CanApplyLocaleImmediately)
+    {
+      SetAppliedLocale(L, FileName);
+      SetResourceModule(Module);
     }
   }
 }
@@ -933,7 +966,7 @@ bool __fastcall TGUIConfiguration::GetCanApplyLocaleImmediately()
     (Screen->DataModuleCount == 0);
 }
 //---------------------------------------------------------------------------
-UnicodeString __fastcall TGUIConfiguration::LocaleCopyright()
+UnicodeString __fastcall TGUIConfiguration::AppliedLocaleCopyright()
 {
   UnicodeString Result;
   if ((FAppliedLocale == 0) || (FAppliedLocale == InternalLocale()))
@@ -949,7 +982,7 @@ UnicodeString __fastcall TGUIConfiguration::LocaleCopyright()
   return Result;
 }
 //---------------------------------------------------------------------------
-UnicodeString __fastcall TGUIConfiguration::LocaleVersion()
+UnicodeString __fastcall TGUIConfiguration::AppliedLocaleVersion()
 {
   UnicodeString Result;
   if ((FAppliedLocale == 0) || (FAppliedLocale == InternalLocale()))
@@ -963,10 +996,10 @@ UnicodeString __fastcall TGUIConfiguration::LocaleVersion()
   return Result;
 }
 //---------------------------------------------------------------------------
-void __fastcall TGUIConfiguration::SetInitialLocale(LCID value)
+void __fastcall TGUIConfiguration::SetAppliedLocale(LCID AppliedLocale, const UnicodeString & LocaleModuleName)
 {
-  FLocale = value;
-  FAppliedLocale = value;
+  FAppliedLocale = AppliedLocale;
+  FLocaleModuleName = LocaleModuleName;
 }
 //---------------------------------------------------------------------------
 void __fastcall TGUIConfiguration::FreeResourceModule(HANDLE Instance)
@@ -1040,95 +1073,127 @@ void __fastcall TGUIConfiguration::FindLocales(const UnicodeString & LocalesMask
   }
 }
 //---------------------------------------------------------------------------
-TStrings * __fastcall TGUIConfiguration::GetLocales()
+void __fastcall TGUIConfiguration::AddLocale(LCID Locale, const UnicodeString & Name)
 {
-  UnicodeString LocalesExts;
-  TStringList * Exts = CreateSortedStringList();
+  std::unique_ptr<TLocaleInfo> LocaleInfo(new TLocaleInfo());
+  LocaleInfo->Locale = Locale;
+  LocaleInfo->Name = Name;
+
   try
   {
-    UnicodeString LocalesMask = ChangeFileExt(ModuleFileName(), L".*");
-    UnicodeString SubLocalesMask = AddTranslationsSubFolder(LocalesMask);
-    FindLocales(SubLocalesMask, Exts, LocalesExts);
-    FindLocales(LocalesMask, Exts, LocalesExts);
-
-    if (FLastLocalesExts != LocalesExts)
+    UnicodeString FileName;
+    HINSTANCE Module = LoadNewResourceModule(Locale, FileName);
+    try
     {
-      FLastLocalesExts = LocalesExts;
-      FLocales->Clear();
+      LocaleInfo->Completeness = GetResourceModuleCompleteness(Module);
+    }
+    __finally
+    {
+      FreeResourceModule(Module);
+    }
+  }
+  catch (...)
+  {
+    LocaleInfo->Completeness = -1;
+  }
 
-      TLanguages * Langs = Languages();
-      int Ext, Index, Count;
-      wchar_t LocaleStr[255];
-      LCID Locale;
+  FLocales->Add(LocaleInfo.release());
+}
+//---------------------------------------------------------------------------
+int __fastcall TGUIConfiguration::LocalesCompare(void * Item1, void * Item2)
+{
+  TLocaleInfo * LocaleInfo1 = static_cast<TLocaleInfo *>(Item1);
+  TLocaleInfo * LocaleInfo2 = static_cast<TLocaleInfo *>(Item2);
+  return CompareText(LocaleInfo1->Name, LocaleInfo2->Name);
+}
+//---------------------------------------------------------------------------
+TObjectList * __fastcall TGUIConfiguration::GetLocales()
+{
+  UnicodeString LocalesMask = ChangeFileExt(ModuleFileName(), L".*");
+  UnicodeString SubLocalesMask = AddTranslationsSubFolder(LocalesMask);
+
+  UnicodeString LocalesExts;
+  std::unique_ptr<TStringList> Exts(CreateSortedStringList());
+  FindLocales(SubLocalesMask, Exts.get(), LocalesExts);
+  FindLocales(LocalesMask, Exts.get(), LocalesExts);
+
+  if (FLastLocalesExts != LocalesExts)
+  {
+    FLastLocalesExts = LocalesExts;
+    FLocales->Clear();
+
+    TLanguages * Langs = Languages();
 
-      Count = Langs->Count;
-      Index = -1;
-      while (Index < Count)
+    int Count = Langs->Count;
+    int Index = -1;
+    while (Index < Count)
+    {
+      LCID Locale;
+      if (Index >= 0)
       {
-        if (Index >= 0)
+        Locale = Langs->LocaleID[Index];
+        DWORD SubLang = SUBLANGID(Locale);
+        int Ext = Exts->IndexOf(Langs->Ext[Index]);
+        if ((Ext >= 0) && (Exts->Objects[Ext] == NULL))
+        {
+          // noop
+        }
+        else if (SubLang == SUBLANG_DEFAULT)
         {
-          Locale = Langs->LocaleID[Index];
-          Ext = Exts->IndexOf(Langs->Ext[Index]);
-          if (Ext < 0)
+          Ext = Exts->IndexOf(Langs->Ext[Index].SubString(1, 2));
+          if ((Ext >= 0) && (Exts->Objects[Ext] == NULL))
           {
-            Ext = Exts->IndexOf(Langs->Ext[Index].SubString(1, 2));
-            if (Ext >= 0)
-            {
-              Locale = MAKELANGID(PRIMARYLANGID(Locale), SUBLANG_DEFAULT);
-            }
+            Locale = MAKELANGID(PRIMARYLANGID(Locale), SUBLANG_DEFAULT);
           }
+        }
 
-          if (Ext >= 0)
-          {
-            Exts->Objects[Ext] = reinterpret_cast<TObject*>(Locale);
-          }
-          else
-          {
-            Locale = 0;
-          }
+        if (Ext >= 0)
+        {
+          Exts->Objects[Ext] = reinterpret_cast<TObject*>(Locale);
         }
         else
         {
-          Locale = InternalLocale();
+          Locale = 0;
         }
+      }
+      else
+      {
+        Locale = InternalLocale();
+      }
 
-        if (Locale)
-        {
-          UnicodeString Name;
-          GetLocaleInfo(Locale, LOCALE_SENGLANGUAGE,
-            LocaleStr, LENOF(LocaleStr));
-          Name = LocaleStr;
-          Name += L" - ";
-          // LOCALE_SNATIVELANGNAME
-          GetLocaleInfo(Locale, LOCALE_SLANGUAGE,
-            LocaleStr, LENOF(LocaleStr));
-          Name += LocaleStr;
-          FLocales->AddObject(Name, reinterpret_cast<TObject*>(Locale));
-        }
-        Index++;
+      if (Locale)
+      {
+        wchar_t LocaleStr[255];
+        GetLocaleInfo(Locale, LOCALE_SENGLANGUAGE,
+          LocaleStr, LENOF(LocaleStr));
+        UnicodeString Name = LocaleStr;
+        Name += L" - ";
+        // LOCALE_SNATIVELANGNAME
+        GetLocaleInfo(Locale, LOCALE_SLANGUAGE,
+          LocaleStr, LENOF(LocaleStr));
+        Name += LocaleStr;
+        AddLocale(Locale, Name);
       }
+      Index++;
+    }
 
-      for (int Index = 0; Index < Exts->Count; Index++)
+    for (int Index = 0; Index < Exts->Count; Index++)
+    {
+      if ((Exts->Objects[Index] == NULL) &&
+          (Exts->Strings[Index].Length() == 3) &&
+          SameText(Exts->Strings[Index].SubString(1, 2), AdditionaLanguagePrefix))
       {
-        if ((Exts->Objects[Index] == NULL) &&
-            (Exts->Strings[Index].Length() == 3) &&
-            SameText(Exts->Strings[Index].SubString(1, 2), AdditionaLanguagePrefix))
+        UnicodeString ModulePath = ChangeFileExt(ModuleFileName(), UnicodeString(L".") + Exts->Strings[Index]);
+        ModulePath = GetTranslationModule(ModulePath);
+        UnicodeString LangName = GetFileFileInfoString(L"LangName", ModulePath);
+        if (!LangName.IsEmpty())
         {
-          UnicodeString ModulePath = ChangeFileExt(ModuleFileName(), UnicodeString(L".") + Exts->Strings[Index]);
-          ModulePath = GetTranslationModule(ModulePath);
-          UnicodeString LangName = GetFileFileInfoString(L"LangName", ModulePath);
-          if (!LangName.IsEmpty())
-          {
-            FLocales->AddObject(LangName, reinterpret_cast<TObject*>(
-              AdditionaLanguageMask + Exts->Strings[Index][3]));
-          }
+          AddLocale(AdditionaLanguageMask + Exts->Strings[Index][3], LangName);
         }
       }
     }
-  }
-  __finally
-  {
-    delete Exts;
+
+    FLocales->Sort(LocalesCompare);
   }
 
   return FLocales;

+ 24 - 11
source/windows/GUIConfiguration.h

@@ -86,6 +86,14 @@ private:
   bool __fastcall GetEmpty() const;
 };
 //---------------------------------------------------------------------------
+class TLocaleInfo : public TObject
+{
+public:
+  LCID Locale;
+  UnicodeString Name;
+  int Completeness;
+};
+//---------------------------------------------------------------------------
 class TCopyParamList
 {
 friend class TGUIConfiguration;
@@ -146,7 +154,7 @@ private:
 class TGUIConfiguration : public TConfiguration
 {
 private:
-  TStrings * FLocales;
+  TObjectList * FLocales;
   UnicodeString FLastLocalesExts;
   bool FContinueOnError;
   bool FConfirmCommandSession;
@@ -180,22 +188,24 @@ private:
   UnicodeString FChecksumAlg;
   int FSessionReopenAutoIdle;
   LCID FAppliedLocale;
+  // Corresponds to FAppliedLocale
+  UnicodeString FLocaleModuleName;
 
 protected:
   LCID FLocale;
-  UnicodeString FLocaleModuleName;
 
   virtual void __fastcall SaveData(THierarchicalStorage * Storage, bool All);
   virtual void __fastcall LoadData(THierarchicalStorage * Storage);
-  virtual LCID __fastcall GetLocale();
+  LCID __fastcall GetLocale();
   void __fastcall SetLocale(LCID value);
   void __fastcall SetLocaleSafe(LCID value);
-  UnicodeString __fastcall GetLocaleHex();
+  UnicodeString __fastcall GetAppliedLocaleHex();
   virtual HINSTANCE __fastcall LoadNewResourceModule(LCID Locale,
     UnicodeString & FileName);
   HANDLE __fastcall GetResourceModule();
   void __fastcall SetResourceModule(HINSTANCE Instance);
-  TStrings * __fastcall GetLocales();
+  TObjectList * __fastcall GetLocales();
+  void __fastcall AddLocale(LCID Locale, const UnicodeString & Name);
   void __fastcall FreeResourceModule(HANDLE Instance);
   void __fastcall SetDefaultCopyParam(const TGUICopyParamType & value);
   virtual bool __fastcall GetRememberPassword();
@@ -213,12 +223,15 @@ protected:
   void __fastcall SetQueueTransfersLimit(int value);
   void __fastcall SetQueueKeepDoneItems(bool value);
   void __fastcall SetQueueKeepDoneItemsFor(int value);
-  void __fastcall SetLocaleInternal(LCID value, bool Safe);
-  void __fastcall SetInitialLocale(LCID value);
+  void __fastcall SetLocaleInternal(LCID value, bool Safe, bool CompleteOnly);
+  void __fastcall SetAppliedLocale(LCID AppliedLocale, const UnicodeString & LocaleModuleName);
   bool __fastcall GetCanApplyLocaleImmediately();
   UnicodeString __fastcall GetTranslationModule(const UnicodeString & Path);
   UnicodeString __fastcall AddTranslationsSubFolder(const UnicodeString & Path);
   void __fastcall FindLocales(const UnicodeString & LocalesMask, TStrings * Exts, UnicodeString & LocalesExts);
+  virtual int __fastcall GetResourceModuleCompleteness(HINSTANCE Module);
+  virtual bool __fastcall IsTranslationComplete(HINSTANCE Module);
+  static int __fastcall LocalesCompare(void * Item1, void * Item2);
 
 public:
   __fastcall TGUIConfiguration();
@@ -229,8 +242,8 @@ public:
   HANDLE __fastcall ChangeToDefaultResourceModule();
   HANDLE __fastcall ChangeResourceModule(HANDLE Instance);
   LCID __fastcall InternalLocale();
-  UnicodeString __fastcall LocaleCopyright();
-  UnicodeString __fastcall LocaleVersion();
+  UnicodeString __fastcall AppliedLocaleCopyright();
+  UnicodeString __fastcall AppliedLocaleVersion();
   TStoredSessionList * __fastcall SelectPuttySessionsForImport(TStoredSessionList * Sessions, UnicodeString & Error);
   bool __fastcall AnyPuttySessionForImport(TStoredSessionList * Sessions);
 
@@ -248,8 +261,8 @@ public:
   __property bool SessionRememberPassword = { read = FSessionRememberPassword, write = FSessionRememberPassword };
   __property LCID Locale = { read = GetLocale, write = SetLocale };
   __property LCID LocaleSafe = { read = GetLocale, write = SetLocaleSafe };
-  __property UnicodeString LocaleHex = { read = GetLocaleHex };
-  __property TStrings * Locales = { read = GetLocales };
+  __property UnicodeString AppliedLocaleHex = { read = GetAppliedLocaleHex };
+  __property TObjectList * Locales = { read = GetLocales };
   __property UnicodeString PuttyPath = { read = FPuttyPath, write = FPuttyPath };
   __property UnicodeString DefaultPuttyPath = { read = FDefaultPuttyPath };
   __property UnicodeString PSftpPath = { read = FPSftpPath, write = FPSftpPath };

+ 2 - 2
source/windows/Setup.cpp

@@ -804,7 +804,7 @@ UnicodeString __fastcall ProgramUrl(UnicodeString URL)
   UnicodeString Params =
     FORMAT(L"v=%s&lang=%s&isinstalled=%d",
       (CurrentVersionStr,
-      GUIConfiguration->LocaleHex,
+      GUIConfiguration->AppliedLocaleHex,
       int(IsInstalled())));
 
   if (Configuration->IsUnofficial)
@@ -891,7 +891,7 @@ static bool __fastcall DoQueryUpdates(TUpdatesConfiguration & Updates, bool Coll
     URL = WantBetaUrl(URL, false);
     URL += L"&dotnet=" + Updates.DotNetVersion;
     URL += L"&console=" + Updates.ConsoleVersion;
-    UnicodeString LocaleVersion = WinConfiguration->LocaleVersion();
+    UnicodeString LocaleVersion = WinConfiguration->AppliedLocaleVersion();
     if (!LocaleVersion.IsEmpty())
     {
       URL += L"&localever=" + LocaleVersion;

+ 1 - 1
source/windows/Tools.cpp

@@ -566,7 +566,7 @@ void __fastcall ShowHelp(const UnicodeString & AHelpKeyword)
   UnicodeString HelpKeyword = AHelpKeyword;
   const wchar_t FragmentSeparator = L'#';
   UnicodeString HelpPath = CutToChar(HelpKeyword, FragmentSeparator, false);
-  UnicodeString HelpUrl = FMTLOAD(DOCUMENTATION_KEYWORD_URL2, (HelpPath, Configuration->ProductVersion, GUIConfiguration->LocaleHex));
+  UnicodeString HelpUrl = FMTLOAD(DOCUMENTATION_KEYWORD_URL2, (HelpPath, Configuration->ProductVersion, GUIConfiguration->AppliedLocaleHex));
   AddToList(HelpUrl, HelpKeyword, FragmentSeparator);
   OpenBrowser(HelpUrl);
 }

+ 20 - 39
source/windows/WinConfiguration.cpp

@@ -448,15 +448,20 @@ __fastcall TWinConfiguration::TWinConfiguration(): TCustomWinConfiguration()
   UpdateSystemIconFont();
   Default();
 
+  // This matters only if the translations are in the executable folder and auto-loaded by VCL (System.Pas - DelayLoadResourceModule)
   try
   {
-    CheckTranslationVersion(
-      GetResourceModuleName(Application->Name, ModuleFileName().c_str()), true);
+    UnicodeString ResourceModuleName = GetResourceModuleName(Application->Name, ModuleFileName().c_str());
+    CheckTranslationVersion(ResourceModuleName, true);
   }
   catch(Exception & E)
   {
     FInvalidDefaultTranslationMessage = E.Message;
   }
+
+  // Load complete locale according to the UI language
+  SetLocaleInternal(NULL, true, true);
+  FDefaultLocale = AppliedLocale;
 }
 //---------------------------------------------------------------------------
 __fastcall TWinConfiguration::~TWinConfiguration()
@@ -2334,44 +2339,20 @@ void __fastcall TWinConfiguration::CleanupTemporaryFolders(TStrings * Folders)
   }
 }
 //---------------------------------------------------------------------------
-LCID __fastcall TWinConfiguration::GetLocale()
+int __fastcall TWinConfiguration::GetResourceModuleCompleteness(HINSTANCE Module)
 {
-  if (!FLocale)
-  {
-    UnicodeString ResourceModule =
-      GetResourceModuleName(Application->Name, ModuleFileName().c_str());
-    if (!ResourceModule.IsEmpty())
-    {
-      UnicodeString ResourceExt = ExtractFileExt(ResourceModule).UpperCase();
-      ResourceExt.Delete(1, 1);
-
-      TLanguages * Langs = Languages();
-      int Index, Count;
-
-      Count = Langs->Count;
-      Index = 0;
-      while ((Index < Count) && (FLocale == 0))
-      {
-        if (Langs->Ext[Index] == ResourceExt)
-        {
-          SetInitialLocale(Langs->LocaleID[Index]);
-        }
-        else if (Langs->Ext[Index].SubString(1, 2) == ResourceExt)
-        {
-          SetInitialLocale(
-            MAKELANGID(PRIMARYLANGID(Langs->LocaleID[Index]),
-              SUBLANG_DEFAULT));
-        }
-        if (FLocale != 0)
-        {
-          FLocaleModuleName = ResourceModule;
-        }
-        Index++;
-      }
-    }
-  }
-
-  return TCustomWinConfiguration::GetLocale();
+  UnicodeString CompletenessStr = LoadStrFrom(Module, TRANSLATION_COMPLETENESS);
+  return StrToIntDef(CompletenessStr, -1);
+}
+//---------------------------------------------------------------------------
+int __fastcall TWinConfiguration::GetLocaleCompletenessTreshold()
+{
+  return 80;
+}
+//---------------------------------------------------------------------------
+bool __fastcall TWinConfiguration::IsTranslationComplete(HINSTANCE Module)
+{
+  return (GetResourceModuleCompleteness(Module) >= LocaleCompletenessTreshold);
 }
 //---------------------------------------------------------------------------
 HINSTANCE __fastcall TWinConfiguration::LoadNewResourceModule(LCID ALocale,

+ 6 - 1
source/windows/WinConfiguration.h

@@ -441,6 +441,7 @@ private:
   int FLastMachineInstallations;
   __property int LastMachineInstallations = { read = FLastMachineInstallations, write = FLastMachineInstallations };
   int FMachineInstallations;
+  LCID FDefaultLocale;
 
   void __fastcall SetDoubleClickAction(TDoubleClickAction value);
   void __fastcall SetCopyOnDoubleClickConfirmation(bool value);
@@ -535,6 +536,7 @@ private:
   TStrings * __fastcall GetCustomCommandOptions();
   void __fastcall SetCustomCommandOptions(TStrings * value);
   void __fastcall SetLockedInterface(bool value);
+  int __fastcall GetLocaleCompletenessTreshold();
 
   bool __fastcall GetDDExtInstalled();
   void __fastcall AddVersionToHistory();
@@ -564,7 +566,6 @@ protected:
   bool __fastcall SameStringLists(TStrings * Strings1, TStrings * Strings2);
   virtual HINSTANCE __fastcall LoadNewResourceModule(LCID Locale,
     UnicodeString & FileName);
-  virtual LCID __fastcall GetLocale();
   void __fastcall CheckTranslationVersion(const UnicodeString FileName,
     bool InternalLocaleOnError);
   virtual void __fastcall DefaultLocalized();
@@ -574,6 +575,8 @@ protected:
   void __fastcall AskForMasterPassword();
   void __fastcall DoLoadExtensionList(const UnicodeString & Path, const UnicodeString & PathId, TStringList * DeletedExtensions);
   TStrings * __fastcall GetExtensionsPaths();
+  virtual int __fastcall GetResourceModuleCompleteness(HINSTANCE Module);
+  virtual bool __fastcall IsTranslationComplete(HINSTANCE Module);
 
 public:
   __fastcall TWinConfiguration();
@@ -707,6 +710,8 @@ public:
   __property TFont * SystemIconFont = { read = GetSystemIconFont };
   __property TStrings * CustomCommandOptions = { read = GetCustomCommandOptions, write = SetCustomCommandOptions };
   __property bool LockedInterface = { read = FLockedInterface, write = SetLockedInterface };
+  __property LCID DefaultLocale = { read = FDefaultLocale };
+  __property int LocaleCompletenessTreshold = { read = GetLocaleCompletenessTreshold };
 };
 //---------------------------------------------------------------------------
 class TCustomCommandType

+ 2 - 2
source/windows/WinHelp.cpp

@@ -36,14 +36,14 @@ void __fastcall SearchHelp(const UnicodeString & Message)
   // Message goes last, as it may exceed URL parameters limit (2048) and get truncated.
   // And we need to preserve the other parameters.
   OpenBrowser(FMTLOAD(DOCUMENTATION_SEARCH_URL3,
-    (Configuration->ProductVersion, GUIConfiguration->LocaleHex,
+    (Configuration->ProductVersion, GUIConfiguration->AppliedLocaleHex,
      EncodeUrlString(Message))));
 }
 //---------------------------------------------------------------------------
 void __fastcall InitializeWinHelp()
 {
   InitializeCustomHelp(new TWebHelpSystem(
-      Configuration->ProductVersion, GUIConfiguration->LocaleHex));
+      Configuration->ProductVersion, GUIConfiguration->AppliedLocaleHex));
 }
 //---------------------------------------------------------------------------
 void __fastcall FinalizeWinHelp()

+ 2 - 3
source/windows/WinMain.cpp

@@ -352,10 +352,9 @@ void __fastcall UpdateStaticUsage()
   Configuration->Usage->Set(L"WindowsProductType", (static_cast<int>(Type)));
   Configuration->Usage->Set(L"Windows64", IsWin64());
   Configuration->Usage->Set(L"DefaultLocale",
-    // See TGUIConfiguration::GetLocaleHex()
+    // See TGUIConfiguration::GetAppliedLocaleHex()
     IntToHex(static_cast<int>(GetDefaultLCID()), 4));
-  Configuration->Usage->Set(L"Locale",
-    IntToHex(static_cast<int>(WinConfiguration->Locale), 4));
+  Configuration->Usage->Set(L"Locale", WinConfiguration->AppliedLocaleHex);
   Configuration->Usage->Set(L"PixelsPerInch", Screen->PixelsPerInch);
 
   bool PixelsPerInchSystemDiffers = false;