Localize
Description
Localize is a simple localization tool, from Pascal Pignard, to help you to edit localization files.
On the same screen you see the master language text and the locale language text for each ressource key. It is thus easy to translate the text.
Supported formats:
- UTF-16 strings files are supported for macOS app localization;
- Latin-1 properties files are supported for Zanyblue app localization.
General notes:
- The localization data must be valid, the program doesn’t make any consistency check;
- Localization comment is put just the line before the related key;
Multi-line comments are gathered in one; - The properties written in file are in alphabetic order of keys with = separator and LF line terminator;
- If two properties have the same key then only the last one is kept.
Specific notes for strings files:
- Processed escaped sequences: \t, \n, \LF, \ », \\, others escaped characters are not processed and written as it is.
- Specific notes for properties files with Zanyblue:
- Only characters valid for Ada identifiers are valid for keys;
- Simple quote must be written twice;
- Processed escaped sequences: \t, \n, \LF, \u, \ , \:, \=, \#, \!, \\, others escaped characters are not processed and written as it is.
Todo (from the author):
- Add more tooltips;
- Add Google translation support;
- Localize the program itself 🙂
Source code
localize.ads
-------------------------------------------------------------------------------
-- NAME (specification) : Localize.ads
-- AUTHOR : Pascal Pignard
-- ROLE : Root unit.
-- NOTES : Ada 2012, GNOGA 2.1 alpha
--
-- COPYRIGHT : (c) Pascal Pignard 2021
-- LICENCE : CeCILL V2 (http://www.cecill.info)
-- CONTACT : http://blady.pagesperso-orange.fr
-------------------------------------------------------------------------------
with UXStrings;
package Localize is
use UXStrings;
subtype String is UXString;
end Localize;
localize-view.ads
-------------------------------------------------------------------------------
-- NAME (specification) : localize-view.ads
-- AUTHOR : Pascal Pignard
-- ROLE : User interface display unit.
-- NOTES : Ada 2012, GNOGA 2.1 alpha
--
-- COPYRIGHT : (c) Pascal Pignard 2021
-- LICENCE : CeCILL V2 (http://www.cecill.info)
-- CONTACT : http://blady.pagesperso-orange.fr
-------------------------------------------------------------------------------
with Gnoga.Gui.Base;
with Gnoga.Gui.View.Grid;
with Gnoga.Gui.Element.Common;
with Gnoga.Gui.Element.Form;
with Gnoga.Gui.Window;
with Localize.Parser;
package Localize.View is
type Default_View_Type is new Gnoga.Gui.View.Grid.Grid_View_Type with record
Main_Window : Gnoga.Gui.Window.Pointer_To_Window_Class;
List_Form : Gnoga.Gui.Element.Form.Form_Type;
Locale_List : Gnoga.Gui.Element.Form.Selection_Type;
Master_Path : Gnoga.Gui.Element.Form.Text_Type;
Locale_Path : Gnoga.Gui.Element.Form.Text_Type;
Load_Button : Gnoga.Gui.Element.Form.Input_Button_Type;
H1, H2, H3 : Gnoga.Gui.Element.Common.HR_Type;
Keys_Label : Gnoga.Gui.Element.Common.DIV_Type;
Key_List : Gnoga.Gui.Element.Form.Selection_Type;
Save_Button : Gnoga.Gui.Element.Form.Input_Button_Type;
Text_Form : Gnoga.Gui.Element.Form.Form_Type;
Select_Pattern : Gnoga.Gui.Element.Form.Search_Type;
Key_Input : Gnoga.Gui.Element.Form.Text_Type;
Duplicate_Button : Gnoga.Gui.Element.Form.Input_Button_Type;
Insert_Button : Gnoga.Gui.Element.Form.Input_Button_Type;
Delete_Button : Gnoga.Gui.Element.Form.Input_Button_Type;
Rename_Button : Gnoga.Gui.Element.Form.Input_Button_Type;
Error_Label : Gnoga.Gui.Element.Common.DIV_Type;
Master_Text : Gnoga.Gui.Element.Form.Text_Area_Type;
Master_Comment : Gnoga.Gui.Element.Form.Text_Area_Type;
Use_Button : Gnoga.Gui.Element.Form.Input_Button_Type;
Locale_Text : Gnoga.Gui.Element.Form.Text_Area_Type;
Locale_Comment : Gnoga.Gui.Element.Form.Text_Area_Type;
Exit_Button : Gnoga.Gui.Element.Form.Input_Button_Type;
Quit_Button : Gnoga.Gui.Element.Form.Input_Button_Type;
Old_Key_Index : Natural;
Master, Locale : Localize.Parser.Property_List;
end record;
type Default_View_Access is access all Default_View_Type;
type Pointer_to_Default_View_Class is access all Default_View_Type'Class;
overriding procedure Create
(Grid : in out Default_View_Type;
Parent : in out Gnoga.Gui.Base.Base_Type'Class;
Layout : in Gnoga.Gui.View.Grid.Grid_Rows_Type;
Fill_Parent : in Boolean := True;
Set_Sizes : in Boolean := True;
ID : in String := "");
end Localize.View;
localize-view.adb
-------------------------------------------------------------------------------
-- NAME (body) : localize-view.adb
-- AUTHOR : Pascal Pignard
-- ROLE : User interface display unit.
-- NOTES : Ada 2012, GNOGA 2.1 alpha
--
-- COPYRIGHT : (c) Pascal Pignard 2021
-- LICENCE : CeCILL V2 (http://www.cecill.info)
-- CONTACT : http://blady.pagesperso-orange.fr
-------------------------------------------------------------------------------
package body Localize.View is
------------
-- Create --
------------
overriding procedure Create
(Grid : in out Default_View_Type;
Parent : in out Gnoga.Gui.Base.Base_Type'Class;
Layout : in Gnoga.Gui.View.Grid.Grid_Rows_Type;
Fill_Parent : in Boolean := True;
Set_Sizes : in Boolean := True;
ID : in String := "")
is
begin
Gnoga.Gui.View.Grid.Grid_View_Type (Grid).Create (Parent, Layout, Fill_Parent, Set_Sizes, ID);
Grid.Panel (1, 1).Border;
Grid.List_Form.Create (Grid.Panel (1, 1).all);
Grid.List_Form.Put ("Master strings file path:");
Grid.Master_Path.Create (Grid.List_Form, 40);
Grid.List_Form.New_Line;
Grid.List_Form.Put ("Locale strings file path:");
Grid.Locale_Path.Create (Grid.List_Form, 40);
Grid.Load_Button.Create (Grid.List_Form, "Load both strings files");
Grid.H1.Create (Grid.List_Form);
Grid.Keys_Label.Create (Grid.List_Form, "Keys (?):");
Grid.Key_List.Create (Grid.List_Form, Visible_Lines => 20);
Grid.List_Form.New_Line;
Grid.List_Form.Put_Line ("(# Master key only, @ Locale key only, * Locale key modified)");
Grid.Save_Button.Create (Grid.List_Form, "Save locale strings file");
Grid.List_Form.New_Line;
Grid.Exit_Button.Create (Grid.List_Form, "Disconnect");
Grid.List_Form.New_Line;
Grid.Quit_Button.Create (Grid.List_Form, "End Localize server");
Grid.Panel (1, 2).Border;
Grid.Text_Form.Create (Grid.Panel (1, 2).all);
Grid.Text_Form.Put ("Select keys: ");
Grid.Select_Pattern.Create (Grid.Text_Form);
Grid.Text_Form.New_Line;
Grid.Text_Form.Put ("Key: ");
Grid.Key_Input.Create (Grid.Text_Form);
Grid.Text_Form.New_Line;
Grid.Duplicate_Button.Create (Grid.Text_Form, "Duplicate");
Grid.Duplicate_Button.Advisory_Title ("Duplicate from master to locale");
Grid.Insert_Button.Create (Grid.Text_Form, "Insert");
Grid.Delete_Button.Create (Grid.Text_Form, "Delete");
Grid.Rename_Button.Create (Grid.Text_Form, "Rename");
Grid.Text_Form.New_Line;
Grid.Error_Label.Create (Grid.Text_Form, "No error.");
Grid.H2.Create (Grid.Text_Form);
Grid.Text_Form.Put_Line ("Master text:");
Grid.Master_Text.Create (Grid.Text_Form, 40, 4);
Grid.Master_Text.Read_Only;
Grid.Text_Form.Put_Line ("Locale text:");
Grid.Locale_Text.Create (Grid.Text_Form, 40, 4);
Grid.H3.Create (Grid.Text_Form);
Grid.Text_Form.Put_Line ("Master comment:");
Grid.Master_Comment.Create (Grid.Text_Form, 40, 4);
Grid.Master_Comment.Read_Only;
Grid.Text_Form.Put_Line ("Locale comment:");
Grid.Locale_Comment.Create (Grid.Text_Form, 40, 4);
end Create;
end Localize.View;
localize-controller.ads
-------------------------------------------------------------------------------
-- NAME (specification) : localize-controller.ads
-- AUTHOR : Pascal Pignard
-- ROLE : User interface control unit.
-- NOTES : Ada 2012, GNOGA 2.1 alpha
--
-- COPYRIGHT : (c) Pascal Pignard 2021
-- LICENCE : CeCILL V2 (http://www.cecill.info)
-- CONTACT : http://blady.pagesperso-orange.fr
-------------------------------------------------------------------------------
with Gnoga.Gui.Window;
with Gnoga.Application.Multi_Connect;
package Localize.Controller is
procedure Default
(Main_Window : in out Gnoga.Gui.Window.Window_Type'Class;
Connection : access Gnoga.Application.Multi_Connect.Connection_Holder_Type);
end Localize.Controller;
localize-controller.adb
-------------------------------------------------------------------------------
-- NAME (body) : localize-controller.adb
-- AUTHOR : Pascal Pignard
-- ROLE : User interface control unit.
-- NOTES : Ada 2012, GNOGA 2.1 alpha
--
-- COPYRIGHT : (c) Pascal Pignard 2021
-- LICENCE : CeCILL V2 (http://www.cecill.info)
-- CONTACT : http://blady.pagesperso-orange.fr
-------------------------------------------------------------------------------
with Gnoga.Gui.Base;
with Gnoga.Gui.View.Grid;
with Localize.View;
with Localize.Parser;
package body Localize.Controller is
-- Handlers
procedure On_Exit (Object : in out Gnoga.Gui.Base.Base_Type'Class);
procedure On_Quit (Object : in out Gnoga.Gui.Base.Base_Type'Class);
procedure On_Load (Object : in out Gnoga.Gui.Base.Base_Type'Class);
procedure On_Save (Object : in out Gnoga.Gui.Base.Base_Type'Class);
procedure On_Change_Key (Object : in out Gnoga.Gui.Base.Base_Type'Class);
procedure On_Change_Text (Object : in out Gnoga.Gui.Base.Base_Type'Class);
procedure On_Change_Comment (Object : in out Gnoga.Gui.Base.Base_Type'Class);
procedure On_Select (Object : in out Gnoga.Gui.Base.Base_Type'Class);
procedure On_Enter (Object : in out Gnoga.Gui.Base.Base_Type'Class) is null;
procedure On_Duplicate (Object : in out Gnoga.Gui.Base.Base_Type'Class);
procedure On_Insert (Object : in out Gnoga.Gui.Base.Base_Type'Class);
procedure On_Delete (Object : in out Gnoga.Gui.Base.Base_Type'Class);
procedure On_Rename (Object : in out Gnoga.Gui.Base.Base_Type'Class);
function Modified
(Properties : Localize.Parser.Property_List;
Key : String)
return Character is (if Localize.Parser.Modified (Properties, Key) then '*' else ' ');
function Not_In
(Properties : Localize.Parser.Property_List;
Key : String;
Tag : Character)
return Character is (if not Localize.Parser.Contains (Properties, Key) then Tag else ' ');
procedure On_Exit (Object : in out Gnoga.Gui.Base.Base_Type'Class) is
View : constant Localize.View.Default_View_Access :=
Localize.View.Default_View_Access (Object.Parent.Parent.Parent);
Dummy_Last_View : Gnoga.Gui.View.View_Type;
begin
View.Remove;
Dummy_Last_View.Create (View.Main_Window.all);
Dummy_Last_View.Put_Line ("Disconnected!");
View.Main_Window.Close;
View.Main_Window.Close_Connection;
end On_Exit;
procedure On_Quit (Object : in out Gnoga.Gui.Base.Base_Type'Class) is
View : constant Localize.View.Default_View_Access :=
Localize.View.Default_View_Access (Object.Parent.Parent.Parent);
Dummy_Last_View : Gnoga.Gui.View.View_Type;
begin
View.Remove;
Dummy_Last_View.Create (View.Main_Window.all);
Dummy_Last_View.Put_Line ("Localize server ended!");
Gnoga.Application.Multi_Connect.End_Application;
end On_Quit;
procedure On_Change_Key (Object : in out Gnoga.Gui.Base.Base_Type'Class) is
View : constant Localize.View.Default_View_Access :=
Localize.View.Default_View_Access (Object.Parent.Parent.Parent);
Key : constant String := View.Key_List.Value;
begin
View.Error_Label.Text ("No error.");
View.Key_Input.Value (Key);
View.Master_Text.Value (Localize.Parser.Text (View.Master, Key));
View.Master_Comment.Value (Localize.Parser.Comment (View.Master, Key));
View.Locale_Text.Value (Localize.Parser.Text (View.Locale, Key));
View.Locale_Comment.Value (Localize.Parser.Comment (View.Locale, Key));
View.Old_Key_Index := View.Key_List.Selected_Index;
exception
when others =>
View.Error_Label.Text ("Error CK with key: " & Key);
end On_Change_Key;
procedure On_Change_Text (Object : in out Gnoga.Gui.Base.Base_Type'Class) is
View : constant Localize.View.Default_View_Access :=
Localize.View.Default_View_Access (Object.Parent.Parent.Parent);
Key_Index : constant Natural := View.Old_Key_Index;
Key : constant String := (if Key_Index > 0 then View.Key_List.Value (Key_Index) else "");
begin
View.Error_Label.Text ("No error.");
Localize.Parser.Text (View.Locale, Key, View.Locale_Text.Value);
if Localize.Parser.Modified (View.Locale, Key) then
View.Key_List.Text
(Key_Index,
From_Latin_1
(Not_In (View.Master, Key, '@') & Not_In (View.Locale, Key, '#') & Modified (View.Locale, Key)) &
Key);
end if;
exception
when others =>
View.Error_Label.Text ("Error CT with key: " & Key);
end On_Change_Text;
procedure On_Change_Comment (Object : in out Gnoga.Gui.Base.Base_Type'Class) is
View : constant Localize.View.Default_View_Access :=
Localize.View.Default_View_Access (Object.Parent.Parent.Parent);
Key_Index : constant Natural := View.Old_Key_Index;
Key : constant String := (if Key_Index > 0 then View.Key_List.Value (Key_Index) else "");
begin
View.Error_Label.Text ("No error.");
Localize.Parser.Comment (View.Locale, Key, View.Locale_Comment.Value);
if Localize.Parser.Modified (View.Locale, Key) then
View.Key_List.Text
(Key_Index,
From_Latin_1
(Not_In (View.Master, Key, '@') & Not_In (View.Locale, Key, '#') & Modified (View.Locale, Key)) &
Key);
end if;
exception
when others =>
View.Error_Label.Text ("Error CC with key: " & Key);
end On_Change_Comment;
procedure On_Load (Object : in out Gnoga.Gui.Base.Base_Type'Class) is
View : constant Localize.View.Default_View_Access :=
Localize.View.Default_View_Access (Object.Parent.Parent.Parent);
begin
View.Error_Label.Text ("No error.");
Localize.Parser.Read (View.Master, View.Master_Path.Value);
Localize.Parser.Read (View.Locale, View.Locale_Path.Value);
On_Select (Object);
exception
when others =>
View.Error_Label.Text ("Error with files.");
end On_Load;
procedure On_Save (Object : in out Gnoga.Gui.Base.Base_Type'Class) is
View : constant Localize.View.Default_View_Access :=
Localize.View.Default_View_Access (Object.Parent.Parent.Parent);
Key : constant String := View.Key_List.Value;
begin
View.Error_Label.Text ("No error.");
Localize.Parser.Write (View.Locale, View.Locale_Path.Value);
Localize.Parser.Reset_Modified_Indicators (View.Locale);
On_Select (Object);
exception
when others =>
View.Error_Label.Text ("Error with file or key: " & Key);
end On_Save;
procedure On_Select (Object : in out Gnoga.Gui.Base.Base_Type'Class) is
View : constant Localize.View.Default_View_Access :=
Localize.View.Default_View_Access (Object.Parent.Parent.Parent);
Old_Key : constant String := View.Key_List.Value;
begin
View.Error_Label.Text ("No error.");
View.Old_Key_Index := 0;
View.Key_List.Empty_Options;
for Key of Localize.Parser.Selected_Keys (View.Master, View.Locale, View.Select_Pattern.Value) loop
View.Key_List.Add_Option
(Key,
From_Latin_1
(Not_In (View.Master, Key, '@') & Not_In (View.Locale, Key, '#') & Modified (View.Locale, Key)) &
Key);
if Key = Old_Key then
View.Key_List.Selected (View.Key_List.Length);
end if;
end loop;
View.Keys_Label.Text ("Keys (" & Gnoga.Image (View.Key_List.Length) & "):");
if View.Key_List.Length > 0 then
if View.Key_List.Selected_Index = 0 then
View.Key_List.Selected (1);
end if;
View.Key_List.Focus;
end if;
On_Change_Key (Object);
exception
when others =>
View.Error_Label.Text ("Error S with key: " & Old_Key);
end On_Select;
procedure On_Duplicate (Object : in out Gnoga.Gui.Base.Base_Type'Class) is
View : constant Localize.View.Default_View_Access :=
Localize.View.Default_View_Access (Object.Parent.Parent.Parent);
Key : constant String := View.Key_Input.Value;
begin
if Key /= "" then
View.Error_Label.Text ("No error.");
Localize.Parser.Text (View.Locale, Key, Localize.Parser.Text (View.Master, Key));
Localize.Parser.Comment (View.Locale, Key, Localize.Parser.Comment (View.Master, Key));
View.Locale_Text.Value (Localize.Parser.Text (View.Master, Key));
View.Locale_Comment.Value (Localize.Parser.Comment (View.Master, Key));
On_Select (Object);
else
View.Error_Label.Text ("Empty key.");
end if;
exception
when others =>
View.Error_Label.Text ("Error with key: " & Key);
end On_Duplicate;
procedure On_Insert (Object : in out Gnoga.Gui.Base.Base_Type'Class) is
View : constant Localize.View.Default_View_Access :=
Localize.View.Default_View_Access (Object.Parent.Parent.Parent);
Key : constant String := View.Key_Input.Value;
begin
if Key /= "" then
View.Error_Label.Text ("No error.");
Localize.Parser.Insert (View.Locale, Key);
On_Select (Object);
else
View.Error_Label.Text ("Empty key.");
end if;
exception
when others =>
View.Error_Label.Text ("Error with key: " & Key);
end On_Insert;
procedure On_Delete (Object : in out Gnoga.Gui.Base.Base_Type'Class) is
View : constant Localize.View.Default_View_Access :=
Localize.View.Default_View_Access (Object.Parent.Parent.Parent);
Key : constant String := View.Key_Input.Value;
begin
if Key /= "" then
View.Error_Label.Text ("No error.");
Localize.Parser.Delete (View.Locale, Key);
On_Select (Object);
else
View.Error_Label.Text ("Empty key.");
end if;
exception
when others =>
View.Error_Label.Text ("Error with key: " & Key);
end On_Delete;
procedure On_Rename (Object : in out Gnoga.Gui.Base.Base_Type'Class) is
View : constant Localize.View.Default_View_Access :=
Localize.View.Default_View_Access (Object.Parent.Parent.Parent);
From : constant String := View.Key_List.Value;
To : constant String := View.Key_Input.Value;
begin
if From /= "" and To /= "" then
View.Error_Label.Text ("No error.");
Localize.Parser.Rename (View.Locale, From, To);
On_Select (Object);
else
View.Error_Label.Text ("Empty key.");
end if;
exception
when others =>
View.Error_Label.Text ("Error with keys: " & From & " and " & To);
end On_Rename;
procedure Default
(Main_Window : in out Gnoga.Gui.Window.Window_Type'Class;
Connection : access Gnoga.Application.Multi_Connect.Connection_Holder_Type)
is
pragma Unreferenced (Connection);
View : constant Localize.View.Default_View_Access := new Localize.View.Default_View_Type;
begin
View.Dynamic;
View.Main_Window := Main_Window'Unchecked_Access;
View.Old_Key_Index := 0;
View.Create (Main_Window, Gnoga.Gui.View.Grid.Horizontal_Split);
-- Avoid Enter to bubble up a new connection
View.On_Submit_Handler (On_Enter'Access);
View.Load_Button.On_Click_Handler (On_Load'Access);
View.Key_List.On_Change_Handler (On_Change_Key'Access);
View.Key_List.On_Click_Handler (On_Change_Key'Access);
View.Save_Button.On_Click_Handler (On_Save'Access);
View.Exit_Button.On_Click_Handler (On_Exit'Access);
View.Quit_Button.On_Click_Handler (On_Quit'Access);
View.Select_Pattern.On_Change_Handler (On_Select'Access);
View.Duplicate_Button.On_Click_Handler (On_Duplicate'Access);
View.Insert_Button.On_Click_Handler (On_Insert'Access);
View.Delete_Button.On_Click_Handler (On_Delete'Access);
View.Rename_Button.On_Click_Handler (On_Rename'Access);
View.Locale_Text.On_Change_Handler (On_Change_Text'Access);
View.Locale_Comment.On_Change_Handler (On_Change_Comment'Access);
end Default;
begin
Gnoga.Application.Multi_Connect.On_Connect_Handler (Default'Access, "default");
end Localize.Controller;
localize-parser.ads
-------------------------------------------------------------------------------
-- NAME (specification) : localize-parser.ads
-- AUTHOR : Pascal Pignard
-- ROLE : Localization files parser unit.
-- NOTES : Ada 2012, GNOGA 2.1 alpha
--
-- COPYRIGHT : (c) Pascal Pignard 2021
-- LICENCE : CeCILL V2 (http://www.cecill.info)
-- CONTACT : http://blady.pagesperso-orange.fr
-------------------------------------------------------------------------------
with Ada.Containers.Indefinite_Vectors;
with Ada.Containers.Ordered_Maps;
package Localize.Parser is
package Lists is new Ada.Containers.Indefinite_Vectors (Positive, String);
subtype Key_List is Lists.Vector;
type Property_List is private;
procedure Read
(Properties : out Property_List;
File_Name : String);
procedure Write
(Properties : Property_List;
File_Name : String);
function Keys
(Properties : Property_List)
return Key_List;
function Selected_Keys
(Master, Locale : Property_List;
Pattern : String)
return Key_List;
function Contains
(Properties : Property_List;
Key : String)
return Boolean;
function Text
(Properties : Property_List;
Key : String)
return String;
procedure Text
(Properties : in out Property_List;
Key : String;
Value : String);
function Comment
(Properties : Property_List;
Key : String)
return String;
procedure Comment
(Properties : in out Property_List;
Key : String;
Value : String);
procedure Insert
(Properties : in out Property_List;
Key : String);
procedure Delete
(Properties : in out Property_List;
Key : String);
procedure Rename
(Properties : in out Property_List;
From, To : String);
function Modified
(Properties : Property_List;
Key : String)
return Boolean;
procedure Reset_Modified_Indicators (Properties : in out Property_List);
private
type Property_Type is record
Comment : String;
Text : String;
Modified : Boolean;
end record;
package Content_Maps is new Ada.Containers.Ordered_Maps (String, Property_Type);
type Property_List is new Content_Maps.Map with null record;
end Localize.Parser;
localize-parser.adb
-------------------------------------------------------------------------------
-- NAME (body) : localize-parser.adb
-- AUTHOR : Pascal Pignard
-- ROLE : Localization files parser unit.
-- NOTES : Ada 2012, GNOGA 2.1 alpha
--
-- COPYRIGHT : (c) Pascal Pignard 2021
-- LICENCE : CeCILL V2 (http://www.cecill.info)
-- CONTACT : http://blady.pagesperso-orange.fr
-------------------------------------------------------------------------------
with Ada.Characters.Wide_Wide_Latin_1;
with Gnoga;
with UXStrings.Formatting;
with UXStrings.Text_IO;
package body Localize.Parser is
use type Content_Maps.Cursor;
procedure Parse_Strings_File
(File_Name : String;
Content : out Property_List)
is
type State_Type is (None, In_Comment, In_Key, In_Value, Equal, Semi_Colon);
Raw_File : UXStrings.Text_IO.File_Type;
Text : String;
C : Unicode_Character;
I : Natural;
State : State_Type := None;
Comment : String;
Key : String;
Value : String;
procedure Append (C : Unicode_Character) is
begin
case State is
when In_Comment =>
Append (Comment, C);
when In_Key =>
Append (Key, C);
when In_Value =>
Append (Value, C);
when others =>
null;
end case;
end Append;
begin
UXStrings.Text_IO.Open (Raw_File, UXStrings.Text_IO.In_File, File_Name, UTF_16LE, UXStrings.Text_IO.LF_Ending);
while not UXStrings.Text_IO.End_Of_File (Raw_File) loop
UXStrings.Text_IO.Get (Raw_File, C);
Text.Append (C);
end loop;
UXStrings.Text_IO.Close (Raw_File);
Content.Clear;
I := Text.First;
while I <= Text.Last loop
if Text (I) = '\' then
Text.Next (I);
case Text (I) is
when 't' =>
Append (Ada.Characters.Wide_Wide_Latin_1.HT);
when 'n' =>
Append (Ada.Characters.Wide_Wide_Latin_1.LF);
when Ada.Characters.Wide_Wide_Latin_1.LF =>
null;
when '"' | '\' =>
Append (Text (I));
when others =>
Append ('\');
Append (Text (I));
end case;
elsif Text (I) = '/' and State = None then
Text.Next (I);
if Text (I) = '*' then
State := In_Comment;
end if;
elsif Text (I) /= '*' and State = In_Comment then
Append (Comment, Text (I));
elsif Text (I) = '*' and State = In_Comment then
Text.Next (I);
if Text (I) = '/' then
State := None;
else
Append (Comment, '*');
Append (Comment, Text (I));
end if;
elsif Text (I) = '"' and State = None then
State := In_Key;
elsif Text (I) /= '"' and State = In_Key then
Append (Key, Text (I));
elsif Text (I) = '"' and State = In_Key then
State := Equal;
elsif Text (I) = '"' and State = Equal then
State := In_Value;
elsif Text (I) /= '"' and State = In_Value then
Append (Value, Text (I));
elsif Text (I) = '"' and State = In_Value then
State := Semi_Colon;
elsif Text (I) = ';' and State = Semi_Colon then
Content.Insert (Key, (Comment, Value, False));
Comment := Null_UXString;
Key := Null_UXString;
Value := Null_UXString;
State := None;
end if;
Text.Next (I);
end loop;
end Parse_Strings_File;
procedure Write_Strings_File
(File_Name : String;
Content : Property_List)
is
Raw_File : UXStrings.Text_IO.File_Type;
procedure Escaped_Put
(Str : String;
Multi_Comment : Boolean := False)
is
begin
for I in 1 .. Length (Str) loop
if Element (Str, I) = Ada.Characters.Wide_Wide_Latin_1.HT then
UXStrings.Text_IO.Put (Raw_File, '\');
UXStrings.Text_IO.Put (Raw_File, 't');
elsif Element (Str, I) = Ada.Characters.Wide_Wide_Latin_1.LF then
if Multi_Comment then
UXStrings.Text_IO.New_Line (Raw_File);
else
UXStrings.Text_IO.Put (Raw_File, '\');
UXStrings.Text_IO.Put (Raw_File, 'n');
end if;
elsif Element (Str, I) in '"' | '\' then
UXStrings.Text_IO.Put (Raw_File, '\');
UXStrings.Text_IO.Put (Raw_File, Element (Str, I));
else
UXStrings.Text_IO.Put (Raw_File, Element (Str, I));
end if;
end loop;
end Escaped_Put;
begin
UXStrings.Text_IO.Create (Raw_File, UXStrings.Text_IO.Out_File, File_Name, UTF_16LE, UXStrings.Text_IO.LF_Ending);
UXStrings.Text_IO.Put_BOM (Raw_File);
for C in Content.Iterate loop
if Content_Maps.Element (C).Comment /= Null_UXString then
UXStrings.Text_IO.Put (Raw_File, '/');
UXStrings.Text_IO.Put (Raw_File, '*');
Escaped_Put (Content_Maps.Element (C).Comment, True);
UXStrings.Text_IO.Put (Raw_File, '*');
UXStrings.Text_IO.Put (Raw_File, '/');
UXStrings.Text_IO.New_Line (Raw_File);
end if;
UXStrings.Text_IO.Put (Raw_File, '"');
Escaped_Put (Content_Maps.Key (C));
UXStrings.Text_IO.Put (Raw_File, '"');
UXStrings.Text_IO.Put (Raw_File, ' ');
UXStrings.Text_IO.Put (Raw_File, '=');
UXStrings.Text_IO.Put (Raw_File, ' ');
UXStrings.Text_IO.Put (Raw_File, '"');
Escaped_Put (Content_Maps.Element (C).Text);
UXStrings.Text_IO.Put (Raw_File, '"');
UXStrings.Text_IO.Put (Raw_File, ';');
UXStrings.Text_IO.New_Line (Raw_File);
end loop;
UXStrings.Text_IO.Close (Raw_File);
end Write_Strings_File;
procedure Parse_Properties_File
(File_Name : String;
Content : out Property_List)
is
type State_Type is (None, In_Comment, In_Key, In_Value, Equal);
Raw_File : UXStrings.Text_IO.File_Type;
Text : String;
C : Unicode_Character;
I : Natural;
State : State_Type := None;
Comment : String;
Key : String;
Value : String;
Hex4 : String := "0000";
procedure Append (C : Wide_Wide_Character) is
begin
case State is
when In_Comment =>
Append (Comment, C);
when In_Key =>
Append (Key, C);
when In_Value =>
Append (Value, C);
when None =>
State := In_Key;
Append (Key, C);
when Equal =>
State := In_Value;
Append (Value, C);
end case;
end Append;
begin
UXStrings.Text_IO.Open (Raw_File, UXStrings.Text_IO.In_File, File_Name, Latin_1, UXStrings.Text_IO.LF_Ending);
while not UXStrings.Text_IO.End_Of_File (Raw_File) loop
UXStrings.Text_IO.Get (Raw_File, C);
Text.Append (C);
end loop;
UXStrings.Text_IO.Close (Raw_File);
Content.Clear;
I := Text.First;
while I <= Text.Last loop
if Text (I) = '\' then
Text.Next (I);
case Text (I) is
when 'u' =>
for J in Hex4 loop
Text.Next (I);
exit when I > Text.Last;
Hex4.Replace_Unicode (J, Text (I));
end loop;
Append (Wide_Wide_Character'Val (Gnoga.Value (Hex4, 16)));
when ' ' | ':' | '=' | '#' | '!' | '\' =>
Append (Text (I));
when 't' =>
Append (Ada.Characters.Wide_Wide_Latin_1.HT);
when 'n' =>
Append (Ada.Characters.Wide_Wide_Latin_1.LF);
when Ada.Characters.Wide_Wide_Latin_1.LF =>
null;
when others =>
Append ('\');
Append (Text (I));
end case;
elsif Text (I) = Ada.Characters.Wide_Wide_Latin_1.LF and State = In_Comment then
State := None;
elsif State = In_Comment then
Append (Comment, Text (I));
elsif Text (I) = Ada.Characters.Wide_Wide_Latin_1.LF and State in In_Value | Equal | In_Key then
Content.Include (Key, (Comment, Value, False));
Comment := Null_UXString;
Key := Null_UXString;
Value := Null_UXString;
State := None;
elsif Text (I) in '#' | '!' and State = None then
State := In_Comment;
if Comment /= Null_UXString then
Append (Ada.Characters.Wide_Wide_Latin_1.LF);
end if;
elsif Text (I) in '=' | ':' and State = In_Key then
State := Equal;
elsif Text (I) not in Ada.Characters.Wide_Wide_Latin_1.HT | Ada.Characters.Wide_Wide_Latin_1.LF |
Ada.Characters.Wide_Wide_Latin_1.FF | ' ' and
State in In_Key
then
Append (Key, (Text (I)));
elsif Text (I) not in Ada.Characters.Wide_Wide_Latin_1.HT | Ada.Characters.Wide_Wide_Latin_1.LF |
Ada.Characters.Wide_Wide_Latin_1.FF | ' ' and
State in None
then
State := In_Key;
Append (Key, Text (I));
elsif Text (I) not in Ada.Characters.Wide_Wide_Latin_1.HT | Ada.Characters.Wide_Wide_Latin_1.FF and
State in In_Value
then
Append (Value, Text (I));
elsif Text (I) not in Ada.Characters.Wide_Wide_Latin_1.HT | Ada.Characters.Wide_Wide_Latin_1.FF | ' ' and
State in Equal
then
State := In_Value;
Append (Value, Text (I));
end if;
Text.Next (I);
end loop;
end Parse_Properties_File;
procedure Write_Properties_File
(File_Name : String;
Content : Property_List)
is
Raw_File : UXStrings.Text_IO.File_Type;
type Escape_Space_Type is (No, Start, Full);
procedure Escaped_Write
(Str : String;
Escape_Space : Escape_Space_Type;
Multi_Comment : Boolean := False)
is
Space : Boolean := Escape_Space = Start;
use UXStrings.Formatting;
use all type UXStrings.Formatting.Alignment;
function Format is new Integer_Format (Natural);
function Format_U16
(Item : Natural;
Base : in Number_Base := 16;
PutPlus : in Boolean := False;
Field : in Natural := 4;
Justify : in Alignment := Right;
Fill : in Character := '0')
return UXString renames Format;
begin
for I in 1 .. Length (Str) loop
if Element (Str, I) = Ada.Characters.Wide_Wide_Latin_1.HT then
UXStrings.Text_IO.Put (Raw_File, '\');
UXStrings.Text_IO.Put (Raw_File, 't');
Space := False;
elsif Element (Str, I) = Ada.Characters.Wide_Wide_Latin_1.LF then
if Multi_Comment then
UXStrings.Text_IO.New_Line (Raw_File);
UXStrings.Text_IO.Put (Raw_File, '#');
else
UXStrings.Text_IO.Put (Raw_File, '\');
UXStrings.Text_IO.Put (Raw_File, 'n');
end if;
Space := False;
elsif Element (Str, I) in '=' | ':' | '#' | '!' | '\' and Escape_Space /= No then
UXStrings.Text_IO.Put (Raw_File, '\');
UXStrings.Text_IO.Put (Raw_File, Element (Str, I));
Space := False;
elsif Element (Str, I) = ' ' and (Space or Escape_Space = Full) then
UXStrings.Text_IO.Put (Raw_File, '\');
UXStrings.Text_IO.Put (Raw_File, ' ');
Space := False;
elsif Element (Str, I) < Ada.Characters.Wide_Wide_Latin_1.DEL then
UXStrings.Text_IO.Put (Raw_File, Element (Str, I));
Space := False;
else
UXStrings.Text_IO.Put (Raw_File, '\');
UXStrings.Text_IO.Put (Raw_File, 'u');
declare
Hex4 : constant String := Format_U16 (BMP_Character'Pos (Get_BMP (Str, I, '?')));
begin
for C of Hex4 loop
UXStrings.Text_IO.Put (Raw_File, C);
end loop;
end;
Space := False;
end if;
end loop;
end Escaped_Write;
begin
UXStrings.Text_IO.Create (Raw_File, UXStrings.Text_IO.Out_File, File_Name, Latin_1, UXStrings.Text_IO.LF_Ending);
for C in Content.Iterate loop
if Content_Maps.Element (C).Comment /= Null_UXString then
UXStrings.Text_IO.Put (Raw_File, '#');
Escaped_Write (Content_Maps.Element (C).Comment, No, True);
UXStrings.Text_IO.New_Line (Raw_File);
end if;
Escaped_Write (Content_Maps.Key (C), Full);
UXStrings.Text_IO.Put (Raw_File, '=');
Escaped_Write (Content_Maps.Element (C).Text, Start);
UXStrings.Text_IO.New_Line (Raw_File);
end loop;
UXStrings.Text_IO.Close (Raw_File);
end Write_Properties_File;
procedure Read
(Properties : out Property_List;
File_Name : String)
is
begin
if Tail (File_Name, 8, ' ') = ".strings" then
Parse_Strings_File (File_Name, Properties);
end if;
if Tail (File_Name, 11, ' ') = ".properties" then
Parse_Properties_File (File_Name, Properties);
end if;
end Read;
procedure Write
(Properties : Property_List;
File_Name : String)
is
begin
if Tail (File_Name, 8, ' ') = ".strings" then
Write_Strings_File (File_Name, Properties);
end if;
if Tail (File_Name, 11, ' ') = ".properties" then
Write_Properties_File (File_Name, Properties);
end if;
end Write;
function Keys
(Properties : Property_List)
return Key_List
is
begin
return Result_Key_List : Key_List do
for Index in Properties.Iterate loop
Result_Key_List.Append (Content_Maps.Key (Index));
end loop;
end return;
end Keys;
function Contains
(Properties : Property_List;
Key : String)
return Boolean is (Content_Maps.Contains (Content_Maps.Map (Properties), Key));
procedure Insert
(Properties : in out Property_List;
Key : String)
is
begin
Properties.Insert (Key, (Null_UXString, Null_UXString, False));
end Insert;
procedure Delete
(Properties : in out Property_List;
Key : String)
is
begin
if Properties.Find (Key) /= Content_Maps.No_Element then
Properties.Delete (Key);
end if;
end Delete;
procedure Rename
(Properties : in out Property_List;
From, To : String)
is
begin
Properties.Insert (To, Properties.Element (From));
Properties.Delete (From);
end Rename;
function Text
(Properties : Property_List;
Key : String)
return String
is
begin
if Properties.Find (Key) = Content_Maps.No_Element then
return "";
else
return Properties.Element (Key).Text;
end if;
end Text;
procedure Text
(Properties : in out Property_List;
Key : String;
Value : String)
is
Cursor : constant Content_Maps.Cursor := Properties.Find (Key);
Element : Property_Type;
begin
if Cursor /= Content_Maps.No_Element then
Element := Content_Maps.Element (Cursor);
Element.Text := Value;
if Element.Text /= Content_Maps.Element (Cursor).Text then
Element.Modified := True;
Properties.Replace_Element (Cursor, Element);
end if;
end if;
end Text;
function Comment
(Properties : Property_List;
Key : String)
return String
is
begin
if Properties.Find (Key) = Content_Maps.No_Element then
return Null_UXString;
else
return Properties.Element (Key).Comment;
end if;
end Comment;
procedure Comment
(Properties : in out Property_List;
Key : String;
Value : String)
is
Cursor : constant Content_Maps.Cursor := Properties.Find (Key);
Element : Property_Type;
begin
if Cursor /= Content_Maps.No_Element then
Element := Content_Maps.Element (Cursor);
Element.Comment := Value;
if Element.Comment /= Content_Maps.Element (Cursor).Comment then
Element.Modified := True;
Properties.Replace_Element (Cursor, Element);
end if;
end if;
end Comment;
function Modified
(Properties : Property_List;
Key : String)
return Boolean
is
begin
if Properties.Find (Key) = Content_Maps.No_Element then
return False;
else
return Properties.Element (Key).Modified;
end if;
end Modified;
procedure Reset_Modified_Indicators (Properties : in out Property_List) is
begin
for Element of Properties loop
Element.Modified := False;
end loop;
end Reset_Modified_Indicators;
function Selected_Keys
(Master, Locale : Property_List;
Pattern : String)
return Key_List
is
package Keys_Sorting is new Lists.Generic_Sorting;
begin
if Pattern /= "" then
return Result_Key_List : Key_List do
for Cursor in Master.Iterate loop
if
(Index (Content_Maps.Key (Cursor), Pattern) > 0 or
Index (Content_Maps.Element (Cursor).Text, Pattern) > 0 or
Index (Content_Maps.Element (Cursor).Comment, Pattern) > 0) and
not Result_Key_List.Contains (Content_Maps.Key (Cursor))
then
Result_Key_List.Append (Content_Maps.Key (Cursor));
end if;
end loop;
for Cursor in Locale.Iterate loop
if
(Index (Content_Maps.Key (Cursor), Pattern) > 0 or
Index (Content_Maps.Element (Cursor).Text, Pattern) > 0 or
Index (Content_Maps.Element (Cursor).Comment, Pattern) > 0) and
not Result_Key_List.Contains (Content_Maps.Key (Cursor))
then
Result_Key_List.Append (Content_Maps.Key (Cursor));
end if;
end loop;
end return;
else
return Result_Key_List : Key_List := Keys (Master) do
for Cursor in Locale.Iterate loop
if not Result_Key_List.Contains (Content_Maps.Key (Cursor)) then
Result_Key_List.Append (Content_Maps.Key (Cursor));
end if;
end loop;
Keys_Sorting.Sort (Result_Key_List);
end return;
end if;
end Selected_Keys;
end Localize.Parser;
localize-main.adb
-------------------------------------------------------------------------------
-- NAME (body) : localize-main.adb
-- AUTHOR : Pascal Pignard
-- ROLE : Main unit.
-- NOTES : Ada 2012, GNOGA 2.1 alpha
--
-- COPYRIGHT : (c) Pascal Pignard 2021
-- LICENCE : CeCILL V2 (http://www.cecill.info)
-- CONTACT : http://blady.pagesperso-orange.fr
-------------------------------------------------------------------------------
with Gnoga.Application.Multi_Connect;
-- Needed to register connection in body initialization
with Localize.Controller;
pragma Unreferenced (Localize.Controller);
procedure Localize.Main is
begin
Gnoga.Application.Title (Name => "Localize (V1.1-alpha)");
Gnoga.Application.Multi_Connect.Initialize;
Gnoga.Application.Multi_Connect.Message_Loop;
exception
when E : others =>
Gnoga.Log (E);
end Localize.Main;
Project file
with "settings.gpr";
with "gnoga.gpr";
project Localize is
for Object_Dir use Settings.Obj_Dir;
for Exec_Dir use Settings.Exe_Dir;
for Main use ("localize-main.adb");
for Create_Missing_Dirs use Settings'Create_Missing_Dirs;
package Builder is
for Executable ("localize-main.adb") use "localize";
end Builder;
package Compiler is
for Default_Switches ("Ada") use Settings.Compiler'Default_Switches ("Ada") & "-gnatyN";
end Compiler;
package Binder renames Settings.Binder;
package Linker renames Settings.Linker;
package Pretty_Printer renames Settings.Pretty_Printer;
end Localize;