aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorJeroen van Rijn <Kelimion@users.noreply.github.com>2022-04-29 00:29:55 +0200
committerJeroen van Rijn <Kelimion@users.noreply.github.com>2022-04-29 00:29:55 +0200
commit1289c96e2cf9fdcdb9c4fb4988f73c0e319fb329 (patch)
tree016370a7474409899fe34ccb9432b5c8d78e38ae /core
parentba23bfb7b9eb32eb9bcf22b0364a25b6ae32203e (diff)
[i18n] QT Linguist TS reader.
Diffstat (limited to 'core')
-rw-r--r--core/encoding/xml/xml_reader.odin1
-rw-r--r--core/i18n/example/i18n_example.odin56
-rw-r--r--core/i18n/gettext.odin2
-rw-r--r--core/i18n/i18n.odin21
-rw-r--r--core/i18n/qt_linguist.odin153
5 files changed, 217 insertions, 16 deletions
diff --git a/core/encoding/xml/xml_reader.odin b/core/encoding/xml/xml_reader.odin
index 636dd0ae4..6d0d4e1aa 100644
--- a/core/encoding/xml/xml_reader.odin
+++ b/core/encoding/xml/xml_reader.odin
@@ -87,7 +87,6 @@ Option_Flag :: enum {
If a tag body has a comment, it will be stripped unless this option is given.
*/
Keep_Tag_Body_Comments,
-
}
Option_Flags :: bit_set[Option_Flag; u16]
diff --git a/core/i18n/example/i18n_example.odin b/core/i18n/example/i18n_example.odin
index f9fb2a353..8c173ee4a 100644
--- a/core/i18n/example/i18n_example.odin
+++ b/core/i18n/example/i18n_example.odin
@@ -4,9 +4,9 @@ import "core:mem"
import "core:fmt"
import "core:i18n"
-LOC :: i18n.get
+_T :: i18n.get
-_main :: proc() {
+mo :: proc() {
using fmt
err: i18n.Error
@@ -23,27 +23,60 @@ _main :: proc() {
These are in the .MO catalog.
*/
println("-----")
- println(LOC(""))
+ println(_T(""))
println("-----")
- println(LOC("There are 69,105 leaves here."))
+ println(_T("There are 69,105 leaves here."))
println("-----")
- println(LOC("Hellope, World!"))
+ println(_T("Hellope, World!"))
/*
For ease of use, pluralized lookup can use both singular and plural form as key for the same translation.
*/
println("-----")
- printf(LOC("There is %d leaf.\n", 1), 1)
- printf(LOC("There is %d leaf.\n", 42), 42)
+ printf(_T("There is %d leaf.\n", 1), 1)
+ printf(_T("There is %d leaf.\n", 42), 42)
- printf(LOC("There are %d leaves.\n", 1), 1)
- printf(LOC("There are %d leaves.\n", 42), 42)
+ printf(_T("There are %d leaves.\n", 1), 1)
+ printf(_T("There are %d leaves.\n", 42), 42)
/*
This isn't.
*/
println("-----")
- println(LOC("Come visit us on Discord!"))
+ println(_T("Come visit us on Discord!"))
+}
+
+qt :: proc() {
+ using fmt
+
+ err: i18n.Error
+
+ /*
+ Parse QT file and set it as the active translation so we can omit `get`'s "catalog" parameter.
+ */
+ i18n.ACTIVE, err = i18n.parse_qt(#load("../../../tests/core/assets/XML/nl_NL-qt-ts.ts"))
+ defer i18n.destroy()
+
+ fmt.printf("parse_qt returned %v\n", err)
+ if err != .None {
+ return
+ }
+
+ /*
+ These are in the .TS catalog.
+ */
+ println("--- Page section ---")
+ println("Page:Text for translation =", _T("Page", "Text for translation"))
+ println("-----")
+ println("Page:Also text to translate =", _T("Page", "Also text to translate"))
+ println("-----")
+ println("--- installscript section ---")
+ println("installscript:99 bottles of beer on the wall =", _T("installscript", "99 bottles of beer on the wall"))
+ println("-----")
+ println("--- apple_count section ---")
+ println("apple_count:%d apple(s) =")
+ println("\t 1 =", _T("apple_count", "%d apple(s)", 1))
+ println("\t 42 =", _T("apple_count", "%d apple(s)", 42))
}
main :: proc() {
@@ -53,7 +86,8 @@ main :: proc() {
mem.tracking_allocator_init(&track, context.allocator)
context.allocator = mem.tracking_allocator(&track)
- _main()
+ // mo()
+ qt()
if len(track.allocation_map) > 0 {
println()
diff --git a/core/i18n/gettext.odin b/core/i18n/gettext.odin
index 70c922cfb..54c5a1111 100644
--- a/core/i18n/gettext.odin
+++ b/core/i18n/gettext.odin
@@ -2,7 +2,7 @@ package i18n
/*
A parser for GNU GetText .MO files.
- Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
+ Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
A from-scratch implementation based after the specification found here:
diff --git a/core/i18n/i18n.odin b/core/i18n/i18n.odin
index 1ee19c2b4..36204efd9 100644
--- a/core/i18n/i18n.odin
+++ b/core/i18n/i18n.odin
@@ -2,7 +2,7 @@ package i18n
/*
Internationalization helpers.
- Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
+ Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
List of contributors:
@@ -26,8 +26,11 @@ MAX_PLURALS :: min(max(#config(ODIN_i18N_MAX_PLURAL_FORMS, 10), 1), 255)
/*
The main data structure. This can be generated from various different file formats, as long as we have a parser for them.
*/
+
+Section :: map[string][]string
+
Translation :: struct {
- k_v: map[string]map[string][]string,
+ k_v: map[string]Section, // k_v[section][key][plural_form] = ...
intern: strings.Intern,
pluralize: proc(number: int) -> int,
@@ -39,6 +42,7 @@ Error :: enum {
*/
None = 0,
Empty_Translation_Catalog,
+ Duplicate_Key,
/*
Couldn't find, open or read file.
@@ -57,6 +61,17 @@ Error :: enum {
MO_File_Unsupported_Version,
MO_File_Invalid,
MO_File_Incorrect_Plural_Count,
+
+ /*
+ Qt Linguist *.TS file errors.
+ */
+ TS_File_Parse_Error,
+ TS_File_Expected_Context,
+ TS_File_Expected_Context_Name,
+ TS_File_Expected_Source,
+ TS_File_Expected_Translation,
+ TS_File_Expected_NumerusForm,
+
}
/*
@@ -92,7 +107,7 @@ get_by_section :: proc(section, key: string, number := 0, catalog: ^Translation
if catalog.pluralize != nil {
plural = catalog.pluralize(number)
}
- return get_by_slot(key, plural, catalog)
+ return get_by_slot(section, key, plural, catalog)
}
get :: proc{get_single_section, get_by_section}
diff --git a/core/i18n/qt_linguist.odin b/core/i18n/qt_linguist.odin
new file mode 100644
index 000000000..65d51444e
--- /dev/null
+++ b/core/i18n/qt_linguist.odin
@@ -0,0 +1,153 @@
+package i18n
+/*
+ A parser for Qt Linguist TS files.
+
+ Copyright 2022 Jeroen van Rijn <nom@duclavier.com>.
+ Made available under Odin's BSD-3 license.
+
+ A from-scratch implementation based after the specification found here:
+ https://doc.qt.io/qt-5/linguist-ts-file-format.html
+
+ List of contributors:
+ Jeroen van Rijn: Initial implementation.
+*/
+import "core:os"
+import "core:encoding/xml"
+import "core:strings"
+
+TS_XML_Options := xml.Options{
+ flags = {
+ .Input_May_Be_Modified,
+ .Must_Have_Prolog,
+ .Must_Have_DocType,
+ .Ignore_Unsupported,
+ .Unbox_CDATA,
+ .Decode_SGML_Entities,
+ },
+ expected_doctype = "TS",
+}
+
+parse_qt_linguist_from_slice :: proc(data: []u8, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
+ context.allocator = allocator
+
+ ts, xml_err := xml.parse(data, TS_XML_Options)
+ defer xml.destroy(ts)
+
+ if xml_err != .None || ts.element_count < 1 || ts.elements[0].ident != "TS" || len(ts.elements[0].children) == 0 {
+ return nil, .TS_File_Parse_Error
+ }
+
+ /*
+ Initalize Translation, interner and optional pluralizer.
+ */
+ translation = new(Translation)
+ translation.pluralize = pluralizer
+ strings.intern_init(&translation.intern, allocator, allocator)
+
+ section: ^Section
+
+ for child_id in ts.elements[0].children {
+ // These should be <context>s.
+ child := ts.elements[child_id]
+ if child.ident != "context" {
+ return translation, .TS_File_Expected_Context
+ }
+
+ // Find section name.
+ section_name_id, section_name_found := xml.find_child_by_ident(ts, child_id, "name")
+ if !section_name_found {
+ return translation, .TS_File_Expected_Context_Name,
+ }
+
+ section_name := ts.elements[section_name_id].value
+
+ if section_name not_in translation.k_v {
+ translation.k_v[section_name] = {}
+ }
+ section = &translation.k_v[section_name]
+
+ // Find messages in section.
+ nth: int
+ for {
+ message_id, message_found := xml.find_child_by_ident(ts, child_id, "message", nth)
+ if !message_found {
+ break
+ }
+
+ numerus_tag, _ := xml.find_attribute_val_by_key(ts, message_id, "numerus")
+ has_plurals := numerus_tag == "yes"
+
+ // We must have a <source> = key
+ source_id, source_found := xml.find_child_by_ident(ts, message_id, "source")
+ if !source_found {
+ return translation, .TS_File_Expected_Source
+ }
+
+ // We must have a <translation>
+ translation_id, translation_found := xml.find_child_by_ident(ts, message_id, "translation")
+ if !translation_found {
+ return translation, .TS_File_Expected_Translation
+ }
+
+ source := ts.elements[source_id]
+ xlat := ts.elements[translation_id]
+
+ if source.value in section {
+ return translation, .Duplicate_Key
+ }
+
+ if has_plurals {
+ if xlat.value != "" {
+ return translation, .TS_File_Expected_NumerusForm
+ }
+
+ num_plurals: int
+ for {
+ numerus_id, numerus_found := xml.find_child_by_ident(ts, translation_id, "numerusform", num_plurals)
+ if !numerus_found {
+ break
+ }
+ num_plurals += 1
+ }
+
+ if num_plurals < 2 {
+ return translation, .TS_File_Expected_NumerusForm
+ }
+ section[source.value] = make([]string, num_plurals)
+
+ num_plurals = 0
+ for {
+ numerus_id, numerus_found := xml.find_child_by_ident(ts, translation_id, "numerusform", num_plurals)
+ if !numerus_found {
+ break
+ }
+ numerus := ts.elements[numerus_id]
+ section[source.value][num_plurals] = strings.intern_get(&translation.intern, numerus.value)
+
+ num_plurals += 1
+ }
+ } else {
+ // Single translation
+ section[source.value] = make([]string, 1)
+ section[source.value][0] = strings.intern_get(&translation.intern, xlat.value)
+ }
+
+ nth += 1
+ }
+ }
+
+ return
+}
+
+parse_qt_linguist_file :: proc(filename: string, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
+ context.allocator = allocator
+
+ data, data_ok := os.read_entire_file(filename)
+ defer delete(data)
+
+ if !data_ok { return {}, .File_Error }
+
+ return parse_qt_linguist_from_slice(data, pluralizer)
+}
+
+parse_qt :: proc { parse_qt_linguist_file, parse_qt_linguist_from_slice } \ No newline at end of file