mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-29 02:40:05 +00:00
[i18n] Add tests.
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
//+ignore
|
||||
package i18n
|
||||
|
||||
/*
|
||||
The i18n package is flexible and easy to use.
|
||||
|
||||
It has one call to get a translation: `get`, which the user can alias into something like `T`.
|
||||
|
||||
`get`, referred to as `T` here, has a few different signatures.
|
||||
All of them will return the key if the entry can't be found in the active translation catalog.
|
||||
|
||||
- `T(key)` returns the translation of `key`.
|
||||
- `T(key, n)` returns a pluralized translation of `key` according to value `n`.
|
||||
|
||||
- `T(section, key)` returns the translation of `key` in `section`.
|
||||
- `T(section, key, n)` returns a pluralized translation of `key` in `section` according to value `n`.
|
||||
|
||||
By default lookup take place in the global `i18n.ACTIVE` catalog for ease of use.
|
||||
If you want to override which translation to use, for example in a language preview dialog, you can use the following:
|
||||
|
||||
- `T(key, n, catalog)` returns the pluralized version of `key` from explictly supplied catalog.
|
||||
- `T(section, key, n, catalog)` returns the pluralized version of `key` in `section` from explictly supplied catalog.
|
||||
|
||||
If a catalog has translation contexts or sections, then ommitting it in the above calls looks up in section "".
|
||||
|
||||
The default pluralization rule is n != 1, which is to say that passing n == 1 (or not passing n) returns the singular form.
|
||||
Passing n != 1 returns plural form 1.
|
||||
|
||||
Should a language not conform to this rule, you can pass a pluralizer procedure to the catalog parser.
|
||||
This is a procedure that maps an integer to an integer, taking a value and returning which plural slot should be used.
|
||||
|
||||
You can also assign it to a loaded catalog after parsing, of course.
|
||||
|
||||
Some code examples follow.
|
||||
*/
|
||||
|
||||
/*
|
||||
```cpp
|
||||
import "core:fmt"
|
||||
import "core:text/i18n"
|
||||
|
||||
T :: i18n.get
|
||||
|
||||
mo :: proc() {
|
||||
using fmt
|
||||
|
||||
err: i18n.Error
|
||||
|
||||
/*
|
||||
Parse MO file and set it as the active translation so we can omit `get`'s "catalog" parameter.
|
||||
*/
|
||||
i18n.ACTIVE, err = i18n.parse_mo(#load("translations/nl_NL.mo"))
|
||||
defer i18n.destroy()
|
||||
|
||||
if err != .None { return }
|
||||
|
||||
/*
|
||||
These are in the .MO catalog.
|
||||
*/
|
||||
println("-----")
|
||||
println(T(""))
|
||||
println("-----")
|
||||
println(T("There are 69,105 leaves here."))
|
||||
println("-----")
|
||||
println(T("Hellope, World!"))
|
||||
println("-----")
|
||||
// We pass 1 into `T` to get the singular format string, then 1 again into printf.
|
||||
printf(T("There is %d leaf.\n", 1), 1)
|
||||
// We pass 42 into `T` to get the plural format string, then 42 again into printf.
|
||||
printf(T("There is %d leaf.\n", 42), 42)
|
||||
|
||||
/*
|
||||
This isn't in the translation catalog, so the key is passed back untranslated.
|
||||
*/
|
||||
println("-----")
|
||||
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("translations/nl_NL-qt-ts.ts"))
|
||||
defer i18n.destroy()
|
||||
|
||||
if err != .None {
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
These are in the .TS catalog. As you can see they have sections.
|
||||
*/
|
||||
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))
|
||||
}
|
||||
```
|
||||
*/
|
||||
@@ -1,99 +0,0 @@
|
||||
package i18n_example
|
||||
|
||||
import "core:mem"
|
||||
import "core:fmt"
|
||||
import "core:text/i18n"
|
||||
|
||||
_T :: i18n.get
|
||||
|
||||
mo :: proc() {
|
||||
using fmt
|
||||
|
||||
err: i18n.Error
|
||||
|
||||
/*
|
||||
Parse MO file and set it as the active translation so we can omit `get`'s "catalog" parameter.
|
||||
*/
|
||||
i18n.ACTIVE, err = i18n.parse_mo(#load("nl_NL.mo"))
|
||||
defer i18n.destroy()
|
||||
|
||||
if err != .None { return }
|
||||
|
||||
/*
|
||||
These are in the .MO catalog.
|
||||
*/
|
||||
println("-----")
|
||||
println(_T(""))
|
||||
println("-----")
|
||||
println(_T("There are 69,105 leaves here."))
|
||||
println("-----")
|
||||
println(_T("Hellope, World!"))
|
||||
|
||||
/*
|
||||
For ease of use, pluralized lookup can use both singular and plural form as key for the same translation.
|
||||
This is a quirk of the GetText format which has separate keys for their different plurals.
|
||||
*/
|
||||
println("-----")
|
||||
printf(_T("There is %d leaf.\n", 1), 1)
|
||||
printf(_T("There is %d leaf.\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(_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() {
|
||||
using fmt
|
||||
|
||||
track: mem.Tracking_Allocator
|
||||
mem.tracking_allocator_init(&track, context.allocator)
|
||||
context.allocator = mem.tracking_allocator(&track)
|
||||
|
||||
// mo()
|
||||
qt()
|
||||
|
||||
if len(track.allocation_map) > 0 {
|
||||
println()
|
||||
for _, v in track.allocation_map {
|
||||
printf("%v Leaked %v bytes.\n", v.location, v.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,9 @@ package i18n
|
||||
A from-scratch implementation based after the specification found here:
|
||||
https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
|
||||
|
||||
Options are ignored as they're not applicable to this format.
|
||||
They're part of the signature for consistency with other catalog formats.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
*/
|
||||
@@ -15,7 +18,7 @@ import "core:os"
|
||||
import "core:strings"
|
||||
import "core:bytes"
|
||||
|
||||
parse_mo_from_slice :: proc(data: []u8, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
|
||||
parse_mo_from_slice :: proc(data: []u8, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
|
||||
context.allocator = allocator
|
||||
/*
|
||||
An MO file should have at least a 4-byte magic, 2 x 2 byte version info,
|
||||
@@ -115,7 +118,7 @@ parse_mo_from_slice :: proc(data: []u8, pluralizer: proc(int) -> int = nil, allo
|
||||
return
|
||||
}
|
||||
|
||||
parse_mo_file :: proc(filename: string, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
|
||||
parse_mo_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, data_ok := os.read_entire_file(filename)
|
||||
@@ -123,7 +126,7 @@ parse_mo_file :: proc(filename: string, pluralizer: proc(int) -> int = nil, allo
|
||||
|
||||
if !data_ok { return {}, .File_Error }
|
||||
|
||||
return parse_mo_from_slice(data, pluralizer)
|
||||
return parse_mo_from_slice(data, options, pluralizer, allocator)
|
||||
}
|
||||
|
||||
parse_mo :: proc { parse_mo_file, parse_mo_from_slice }
|
||||
|
||||
@@ -74,6 +74,14 @@ Error :: enum {
|
||||
|
||||
}
|
||||
|
||||
Parse_Options :: struct {
|
||||
merge_sections: bool,
|
||||
}
|
||||
|
||||
DEFAULT_PARSE_OPTIONS :: Parse_Options{
|
||||
merge_sections = false,
|
||||
}
|
||||
|
||||
/*
|
||||
Several ways to use:
|
||||
- get(key), which defaults to the singular form and i18n.ACTIVE catalog, or
|
||||
|
||||
@@ -27,7 +27,7 @@ TS_XML_Options := xml.Options{
|
||||
expected_doctype = "TS",
|
||||
}
|
||||
|
||||
parse_qt_linguist_from_slice :: proc(data: []u8, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
|
||||
parse_qt_linguist_from_slice :: proc(data: []u8, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
ts, xml_err := xml.parse(data, TS_XML_Options)
|
||||
@@ -59,7 +59,7 @@ parse_qt_linguist_from_slice :: proc(data: []u8, pluralizer: proc(int) -> int =
|
||||
return translation, .TS_File_Expected_Context_Name,
|
||||
}
|
||||
|
||||
section_name := ts.elements[section_name_id].value
|
||||
section_name := "" if options.merge_sections else ts.elements[section_name_id].value
|
||||
|
||||
if section_name not_in translation.k_v {
|
||||
translation.k_v[section_name] = {}
|
||||
@@ -139,7 +139,7 @@ parse_qt_linguist_from_slice :: proc(data: []u8, pluralizer: proc(int) -> int =
|
||||
return
|
||||
}
|
||||
|
||||
parse_qt_linguist_file :: proc(filename: string, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
|
||||
parse_qt_linguist_file :: proc(filename: string, options := DEFAULT_PARSE_OPTIONS, pluralizer: proc(int) -> int = nil, allocator := context.allocator) -> (translation: ^Translation, err: Error) {
|
||||
context.allocator = allocator
|
||||
|
||||
data, data_ok := os.read_entire_file(filename)
|
||||
@@ -147,7 +147,7 @@ parse_qt_linguist_file :: proc(filename: string, pluralizer: proc(int) -> int =
|
||||
|
||||
if !data_ok { return {}, .File_Error }
|
||||
|
||||
return parse_qt_linguist_from_slice(data, pluralizer)
|
||||
return parse_qt_linguist_from_slice(data, options, pluralizer, allocator)
|
||||
}
|
||||
|
||||
parse_qt :: proc { parse_qt_linguist_file, parse_qt_linguist_from_slice }
|
||||
Reference in New Issue
Block a user