Skip to content

Commit

Permalink
Automatic localization integration (#20)
Browse files Browse the repository at this point in the history
* Added automatic localization addition

Settings now has a PoolStringArray of locales (in place of the constants) and a equally indexed PoolStringArray of language names.
Localization files now are required to provide a name via the LANGUAGE_NAME key which must be in the first line of the file.
SettingsDialog now automatically sets the items in the OptionButton.

* Sorted languages alphabetically, with English on top

Separated the adding of items to its own function. which adds them in alphabetical order, storing the original position in the id.
Also changed the callback method for the OptionButton to use id and not index to set the locale.
And initialized locales and language_names on declaration to clear warnings.

* Moved colon

To comply with project's coding style

* Added ParseResult inner class in I18nParser

Added a ParseResult class, which contains two PoolStringArrays, locales and language_names.
load_files() now returns a ParseResult.

* Changed 'not' to '!'

* Fixed languages not appearing bug

It was caused by trying to modify a class member of type PoolStringArray, which is passed by value  and not by reference.
Fix: add a setter method to the ParseResult class.
Also used one method for both arrays, since elements should always be added in pairs.

* Update i18n.md

Updated docs to comply with new localization system

Co-authored-by: Marcus Brummer <mbrlabs@users.noreply.github.com>
  • Loading branch information
Astrono2 and mbrlabs committed Jun 11, 2021
1 parent a29fb45 commit f6b392a
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 29 deletions.
5 changes: 3 additions & 2 deletions docs/i18n.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ Godot has two ways to handle multiple languages right now:
The CSV file way of doing things is fine if you work alone, but once you have multiple people working on translations you will get lot's of merge conflicts.
Gettext is more powerful and splits translations into seperate files, but i don't like the format.

However, you can add translations in code by using the TranslationServer. My localization files are simple `.txt` files. This makes translating strings really easy. The file format is very straight forward: One translation per line; then a key followed by the translation (sperated by at least one whitespace). Here is an example for `English`:
However, you can add translations in code by using the TranslationServer. My localization files are simple `.txt` files. This makes translating strings really easy. The file format is very straight forward: One translation per line; then a key followed by the translation (sperated by at least one whitespace). The first key **must be** `LANGUAGE_NAME` followed by the language's name (in that same language, not in english). Here is an example for `English`:

`en.txt`:

```
LANGUAGE_NAME English
GREETING Hello World!
SAVE Save
Expand All @@ -28,5 +30,4 @@ TOOL_BRUSH Brush Tool

If you want to add more translations all you need to do is:
- Create a new translation file in `Assets/i18n/` using an external text editor
- Add the new language to the settings dialog
- The `.txt` file will be loaded automatically (see `Misc/I18NParser.gd`)
2 changes: 2 additions & 0 deletions lorien/Assets/I18n/de.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
LANGUAGE_NAME Deutsch

# --------------------------------------------------------------------------------
# Menu
# --------------------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions lorien/Assets/I18n/en.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
LANGUAGE_NAME English

# -----------------------------------------------------------------------------
# Menu strings
# -----------------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions lorien/Assets/I18n/es.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
LANGUAGE_NAME Español

# -----------------------------------------------------------------------------
# Menu strings
# -----------------------------------------------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions lorien/Assets/I18n/it.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
LANGUAGE_NAME Italiano

# -----------------------------------------------------------------------------
# Menu
# -----------------------------------------------------------------------------
Expand Down
22 changes: 20 additions & 2 deletions lorien/Misc/I18nParser.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,29 @@ class_name I18nParser
const I18N_FOLDER := "res://Assets/I18n/"

# -------------------------------------------------------------------------------------------------
func load_files() -> void:

class ParseResult:
var locales := PoolStringArray()
var language_names := PoolStringArray()

func append(var locale : String, var name : String) -> void:
locales.append(locale)
language_names.append(name)

# -------------------------------------------------------------------------------------------------
func load_files() -> ParseResult:
var result = ParseResult.new()
for f in _get_i18n_files():
var file := File.new()
print("Loading i18n file: %s" % f)
if file.open(f, File.READ) == OK:
var translation := Translation.new()
translation.locale = f.get_file().get_basename()
var name := file.get_line().strip_edges()
if !name.begins_with("LANGUAGE_NAME"):
printerr("The file must start with 'LANGUAGE_NAME' key.")
continue
name = name.trim_prefix("LANGUAGE_NAME").strip_edges()
while !file.eof_reached():
var line := file.get_line().strip_edges()
if line.length() == 0 || line.begins_with("#"):
Expand All @@ -25,7 +41,9 @@ func load_files() -> void:
else:
printerr("Key not found (make sure to use spaces; not tabs): %s" % line)
TranslationServer.add_translation(translation)

result.append(translation.locale, name)
return result

# -------------------------------------------------------------------------------------------------
func _get_i18n_files() -> Array:
var files := []
Expand Down
7 changes: 6 additions & 1 deletion lorien/Misc/Settings.gd
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,19 @@ const RENDERING_AA_MODE := "rendering_aa_mode"

# -------------------------------------------------------------------------------------------------
var _config_file := ConfigFile.new()
var locales: PoolStringArray
var language_names: PoolStringArray

# -------------------------------------------------------------------------------------------------
func _ready():
_config_file = ConfigFile.new()
_load_settings()

var i18n := I18nParser.new()
i18n.load_files()
var parse_result := i18n.load_files()
TranslationServer.set_locale(get_value(GENERAL_LANGUAGE, "en"))
locales = parse_result.locales
language_names = parse_result.language_names

# -------------------------------------------------------------------------------------------------
func _load_settings() -> int:
Expand Down Expand Up @@ -50,3 +54,4 @@ func set_value(key: String, value = null):
_config_file.set_value(DEFAULT_SECTION, key, value)
_save_settings()


6 changes: 0 additions & 6 deletions lorien/Misc/Types.gd
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
extends Node
class_name Types

# -------------------------------------------------------------------------------------------------
const LOCALE_ENGLISH := "en"
const LOCALE_GERMAN := "de"
const LOCALE_ITALIAN := "it"
const LOCALE_SPANISH := "es"

# -------------------------------------------------------------------------------------------------
enum Tool {
BRUSH,
Expand Down
43 changes: 26 additions & 17 deletions lorien/UI/Dialogs/SettingsDialog.gd
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,6 @@ const AA_NONE_INDEX := 0
const AA_OPENGL_HINT_INDEX := 1
const AA_TEXTURE_FILL_INDEX := 2

const LANGUAGE_ID_ENGLISH := 0
const LANGUAGE_ID_GERMAN := 1
const LANGUAGE_ID_ITALIAN := 2
const LANGUAGE_ID_SPANISH := 3

# -------------------------------------------------------------------------------------------------
onready var _tab_general: Control = $MarginContainer/TabContainer/General
onready var _tab_appearance: Control = $MarginContainer/TabContainer/Appearance
Expand Down Expand Up @@ -43,7 +38,7 @@ func _set_values() -> void:
var project_dir = Settings.get_value(Settings.GENERAL_DEFAULT_PROJECT_DIR, "")
var theme = Settings.get_value(Settings.APPEARANCE_THEME, Types.UITheme.DARK)
var aa_mode = Settings.get_value(Settings.RENDERING_AA_MODE, Config.DEFAULT_AA_MODE)
var language = Settings.get_value(Settings.GENERAL_LANGUAGE, Types.LOCALE_ENGLISH)
var locale = Settings.get_value(Settings.GENERAL_LANGUAGE, "en")

match theme:
Types.UITheme.DARK: _theme.selected = THEME_DARK_INDEX
Expand All @@ -52,17 +47,34 @@ func _set_values() -> void:
Types.AAMode.NONE: _aa_mode.selected = AA_NONE_INDEX
Types.AAMode.OPENGL_HINT: _aa_mode.selected = AA_OPENGL_HINT_INDEX
Types.AAMode.TEXTURE_FILL: _aa_mode.selected = AA_TEXTURE_FILL_INDEX
match language:
Types.LOCALE_ENGLISH: _language_options.selected = LANGUAGE_ID_ENGLISH
Types.LOCALE_GERMAN: _language_options.selected = LANGUAGE_ID_GERMAN
Types.LOCALE_ITALIAN: _language_options.selected = LANGUAGE_ID_ITALIAN
Types.LOCALE_SPANISH: _language_options.selected = LANGUAGE_ID_SPANISH

_set_languages(locale)

_brush_size.value = brush_size
_brush_color.color = brush_color
_canvas_color.color = canvas_color
_project_dir.text = project_dir

# -------------------------------------------------------------------------------------------------
func _set_languages(current_locale: String) -> void:
# Technically, Settings.language_names is useless from here on out, but I figure it's probably gonna come in handy in the future
var sorted_languages := Array(Settings.language_names)
var unsorted_languages := sorted_languages.duplicate()
# English appears at the top, so it mustn't be sorted alphabetically with the rest
sorted_languages.erase("English")
sorted_languages.sort()

# Add English before the rest + a separator so that it doesn't look weird for the alphabetical order to start after it
_language_options.add_item("English", unsorted_languages.find("English"))
_language_options.add_separator()
for lang in sorted_languages:
var id := unsorted_languages.find(lang)
_language_options.add_item(lang, id)

# Set selected
var id := Array(Settings.locales).find(current_locale)
_language_options.selected = _language_options.get_item_index(id)

# -------------------------------------------------------------------------------------------------
func _on_DefaultBrushSize_value_changed(value: int) -> void:
Settings.set_value(Settings.GENERAL_DEFAULT_BRUSH_SIZE, int(value))
Expand Down Expand Up @@ -105,12 +117,9 @@ func _on_AntiAliasing_item_selected(index: int):
_rendering_restart_label.show()

# -------------------------------------------------------------------------------------------------
func _on_OptionButton_item_selected(id: int):
var locale := Types.LOCALE_ENGLISH
match id:
LANGUAGE_ID_GERMAN: locale = Types.LOCALE_GERMAN
LANGUAGE_ID_ITALIAN: locale = Types.LOCALE_ITALIAN
LANGUAGE_ID_SPANISH: locale = Types.LOCALE_SPANISH
func _on_OptionButton_item_selected(idx: int):
var id := _language_options.get_item_id(idx)
var locale: String = Settings.locales[id]

Settings.set_value(Settings.GENERAL_LANGUAGE, locale)
TranslationServer.set_locale(locale)
Expand Down
1 change: 0 additions & 1 deletion lorien/UI/Dialogs/SettingsDialog.tscn
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,6 @@ margin_right = 476.0
margin_bottom = 25.0
size_flags_horizontal = 3
text = "English"
items = [ "English", null, false, 0, null, "Deutsch", null, false, 1, null, "Italiano", null, false, 2, null, "Español", null, false, 3, null ]
selected = 0

[node name="RestartLabel" type="Label" parent="MarginContainer/TabContainer/General/VBoxContainer"]
Expand Down

0 comments on commit f6b392a

Please sign in to comment.