mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-05 15:18:49 +00:00
This change was made in order to allow things produced with Odin and using Odin's core library, to not require the LICENSE to also be distributed alongside the binary form.
232 lines
7.8 KiB
Odin
232 lines
7.8 KiB
Odin
package i18n
|
|
/*
|
|
Internationalization helpers.
|
|
|
|
Copyright 2021-2022 Jeroen van Rijn <nom@duclavier.com>.
|
|
Made available under Odin's license.
|
|
|
|
List of contributors:
|
|
Jeroen van Rijn: Initial implementation.
|
|
*/
|
|
import "core:strings"
|
|
|
|
// Currently active catalog.
|
|
ACTIVE: ^Translation
|
|
|
|
// Allow between 1 and 255 plural forms. Default: 10.
|
|
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]Section, // k_v[section][key][plural_form] = ...
|
|
intern: strings.Intern,
|
|
|
|
pluralize: proc(number: int) -> int,
|
|
}
|
|
|
|
Error :: enum {
|
|
// General return values.
|
|
None = 0,
|
|
Empty_Translation_Catalog,
|
|
Duplicate_Key,
|
|
|
|
// Couldn't find, open or read file.
|
|
File_Error,
|
|
|
|
// File too short.
|
|
Premature_EOF,
|
|
|
|
// GNU Gettext *.MO file errors.
|
|
MO_File_Invalid_Signature,
|
|
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,
|
|
Bad_Str,
|
|
Bad_Id,
|
|
|
|
}
|
|
|
|
Parse_Options :: struct {
|
|
merge_sections: bool,
|
|
}
|
|
|
|
DEFAULT_PARSE_OPTIONS :: Parse_Options{
|
|
merge_sections = false,
|
|
}
|
|
|
|
/*
|
|
Returns the first translation string for the passed `key`.
|
|
It is also aliased with `get()`.
|
|
|
|
Two ways to use it:
|
|
- get(key), which defaults to the `i18n.ACTIVE` catalogue, or
|
|
- get(key, catalog) to grab text from a specific loaded catalogue
|
|
|
|
Inputs:
|
|
- key: the string to translate
|
|
- catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE)
|
|
|
|
Returns: the translated string, or the original `key` if no translation was found.
|
|
*/
|
|
get_single_section :: proc(key: string, catalog: ^Translation = ACTIVE) -> (value: string) {
|
|
return get_by_slot(key, 0, catalog)
|
|
}
|
|
|
|
/*
|
|
Returns the first translation string for the passed `key` in a specific section or context.
|
|
It is also aliases with `get()`.
|
|
|
|
Two ways to use it:
|
|
- get(section, key), which defaults to the `i18n.ACTIVE` catalogue, or
|
|
- get(section, key, catalog) to grab text from a specific loaded catalogue
|
|
|
|
Inputs:
|
|
- section: the catalogue section (sometimes also called 'context') in which to look up the translation
|
|
- key: the string to translate
|
|
- catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE)
|
|
|
|
Returns: the translated string, or the original `key` if no translation was found.
|
|
*/
|
|
get_by_section :: proc(section, key: string, catalog: ^Translation = ACTIVE) -> (value: string) {
|
|
return get_by_slot(section, key, 0, catalog)
|
|
}
|
|
|
|
get :: proc{get_single_section, get_by_section}
|
|
|
|
/*
|
|
Returns the translation string for the passed `key` in a specific plural form (if present in the catalogue).
|
|
It is also aliased with `get_n()`.
|
|
|
|
Two ways to use it:
|
|
- get_n(key, quantity), which returns the appropriate plural from the active catalogue, or
|
|
- get_n(key, quantity, catalog) to grab text from a specific loaded catalogue
|
|
|
|
Inputs:
|
|
- key: the string to translate
|
|
- quantity: the quantity of item to be used to select the correct plural form
|
|
- catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE)
|
|
|
|
Returns: the translated string, or the original `key` if no translation was found.
|
|
*/
|
|
get_single_section_with_quantity :: proc(key: string, quantity: int, catalog: ^Translation = ACTIVE) -> (value: string) {
|
|
/*
|
|
A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule.
|
|
*/
|
|
slot := 1 if quantity != 1 else 0
|
|
|
|
if catalog.pluralize != nil {
|
|
slot = catalog.pluralize(quantity)
|
|
}
|
|
return get_by_slot(key, slot, catalog)
|
|
}
|
|
|
|
/*
|
|
Returns the translation string for the passed `key` in a specific plural form (if present in the catalogue)
|
|
in a specific section or context.
|
|
It is also aliases with `get_n()`.
|
|
|
|
Two ways to use it:
|
|
- get(section, key, quantity), which returns the appropriate plural from the active catalogue, or
|
|
- get(section, key, quantity, catalog) to grab text from a specific loaded catalogue
|
|
|
|
Inputs:
|
|
- section: the catalogue section (sometime also called 'context') from which to lookup the translation
|
|
- key: the string to translate
|
|
- qantity: the quantity of item to be used to select the correct plural form
|
|
- catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE)
|
|
|
|
Returns: the translated string, or the original `key` if no translation was found
|
|
*/
|
|
get_by_section_with_quantity :: proc(section, key: string, quantity: int, catalog: ^Translation = ACTIVE) -> (value: string) {
|
|
/*
|
|
A lot of languages use singular for 1 item and plural for 0 or more than 1 items. This is our default pluralize rule.
|
|
*/
|
|
slot := 1 if quantity != 1 else 0
|
|
|
|
if catalog.pluralize != nil {
|
|
slot = catalog.pluralize(quantity)
|
|
}
|
|
return get_by_slot(section, key, slot, catalog)
|
|
}
|
|
get_n :: proc{get_single_section_with_quantity, get_by_section_with_quantity}
|
|
|
|
/*
|
|
Two ways to use:
|
|
- get_by_slot(key, slot), which returns the requested plural from the active catalogue, or
|
|
- get_by_slot(key, slot, catalog) to grab text from a specific loaded catalogue.
|
|
|
|
If a file format parser doesn't (yet) support plural slots, each of the slots will point at the same string.
|
|
- section: the catalogue section (sometime also called 'context') from which to lookup the translation
|
|
|
|
Inputs:
|
|
- key: the string to translate.
|
|
- slot: the translation slot to choose (slots refer to plural forms specific for each language and their meaning changes from catalogue to catalogue).
|
|
- catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE)
|
|
|
|
Returns: the translated string, or the original `key` if no translation was found.
|
|
*/
|
|
get_by_slot_single_section :: proc(key: string, slot: int, catalog: ^Translation = ACTIVE) -> (value: string) {
|
|
return get_by_slot_by_section("", key, slot, catalog)
|
|
}
|
|
|
|
/*
|
|
Two ways to use:
|
|
- get_by_slot(key, slot), which returns the requested plural from the active catalog, or
|
|
- get_by_slot(key, slot, catalog) to grab text from a specific one.
|
|
|
|
If a file format parser doesn't (yet) support plural slots, each of the slots will point at the same string.
|
|
|
|
Inputs:
|
|
- section: the catalogue section (sometime also called 'context') from which to lookup the translation
|
|
- key: the string to translate.
|
|
- slot: the translation slot to choose (slots refer to plural forms specific for each language and their meaning changes from catalogue to catalogue).
|
|
- catalog: the catalogue to use for the translation (defaults to i18n.ACTIVE)
|
|
|
|
Returns: the translated string or the original `key` if no translation was found.
|
|
*/
|
|
get_by_slot_by_section :: proc(section, key: string, slot: int, catalog: ^Translation = ACTIVE) -> (value: string) {
|
|
if catalog == nil || section not_in catalog.k_v {
|
|
// Return the key if the catalog catalog hasn't been initialized yet, or the section is not present.
|
|
return key
|
|
}
|
|
|
|
// Return the translation from the requested slot if this key is known, else return the key.
|
|
if translations, ok := catalog.k_v[section][key]; ok {
|
|
plural := min(max(0, slot), len(catalog.k_v[section][key]) - 1)
|
|
return translations[plural]
|
|
}
|
|
return key
|
|
}
|
|
get_by_slot :: proc{get_by_slot_single_section, get_by_slot_by_section}
|
|
|
|
/*
|
|
- destroy(), to clean up the currently active catalog catalog i18n.ACTIVE
|
|
- destroy(catalog), to clean up a specific catalog.
|
|
*/
|
|
destroy :: proc(catalog: ^Translation = ACTIVE, allocator := context.allocator) {
|
|
context.allocator = allocator
|
|
|
|
if catalog == nil {
|
|
return
|
|
}
|
|
|
|
for section in catalog.k_v {
|
|
for key in catalog.k_v[section] {
|
|
delete(catalog.k_v[section][key])
|
|
}
|
|
delete(catalog.k_v[section])
|
|
}
|
|
delete(catalog.k_v)
|
|
strings.intern_destroy(&catalog.intern)
|
|
free(catalog)
|
|
} |