Skip to content

Commit be92cc5

Browse files
committed
Add basic translation support
1 parent 86f34a5 commit be92cc5

File tree

12 files changed

+328
-12
lines changed

12 files changed

+328
-12
lines changed

addons/parley/components/default_balloon.gd

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,34 @@ func _set_current_node_asts(p_current_node_asts: Array[ParleyNodeAst]) -> void:
8383
await ready
8484

8585
balloon.show()
86+
# TODO: remove
87+
# current_node_asts = []
88+
# for node_ast: ParleyNodeAst in p_current_node_asts:
89+
# # TODO: add a resolve method to each node
90+
# var resolved_node_ast: ParleyNodeAst
91+
# if node_ast is ParleyDialogueNodeAst:
92+
# var dialogue_node_ast: ParleyDialogueNodeAst = node_ast
93+
# var resolved_text: String = dialogue_sequence_ast.resolve_value(ctx, dialogue_node_ast.text, true, dialogue_node_ast, 'text')
94+
# resolved_node_ast = ParleyDialogueNodeAst.new(
95+
# dialogue_node_ast.id,
96+
# dialogue_node_ast.position,
97+
# dialogue_node_ast.character,
98+
# resolved_text,
99+
# )
100+
# elif node_ast is ParleyDialogueOptionNodeAst:
101+
# var dialogue_option_node_ast: ParleyDialogueOptionNodeAst = node_ast
102+
# var resolved_text: String = dialogue_sequence_ast.resolve_value(ctx, dialogue_option_node_ast.text, true, dialogue_option_node_ast, 'text')
103+
# resolved_node_ast = ParleyDialogueOptionNodeAst.new(
104+
# dialogue_option_node_ast.id,
105+
# dialogue_option_node_ast.position,
106+
# dialogue_option_node_ast.character,
107+
# resolved_text,
108+
# )
109+
# if resolved_node_ast:
110+
# current_node_asts.append(resolved_node_ast)
86111
current_node_asts = p_current_node_asts
87112
var current_children: Array[Node] = balloon_container.get_children()
88-
var first_node: ParleyNodeAst = p_current_node_asts.front()
113+
var first_node: ParleyNodeAst = current_node_asts.front()
89114
var next_children: Array[Node] = await _build_next_children(current_children, first_node)
90115
if next_children.size() == 0:
91116
return

addons/parley/models/dialogue_node_ast.gd

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ static func get_colour() -> Color:
4141

4242

4343
#region UTILS
44+
func to_resolved(resolved_text: String) -> ParleyDialogueNodeAst:
45+
return ParleyDialogueNodeAst.new(
46+
id,
47+
position,
48+
character,
49+
resolved_text
50+
)
51+
52+
4453
func resolve_character() -> ParleyCharacter:
4554
return ParleyCharacterStore.resolve_character_ref(character)
4655
#endregion

addons/parley/models/dialogue_option_node_ast.gd

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ static func get_colour() -> Color:
4141

4242

4343
#region UTILS
44+
func to_resolved(resolved_text: String) -> ParleyDialogueOptionNodeAst:
45+
return ParleyDialogueOptionNodeAst.new(
46+
id,
47+
position,
48+
character,
49+
resolved_text
50+
)
51+
52+
4453
func resolve_character() -> ParleyCharacter:
4554
return ParleyCharacterStore.resolve_character_ref(character)
4655
#endregion

addons/parley/models/dialogue_sequence_ast.gd

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -357,11 +357,23 @@ func next(ctx: ParleyContext, current_node: ParleyNodeAst = null, dry_run: bool
357357
continue
358358
var next_node: ParleyNodeAst = filtered_next_nodes.front()
359359
var next_type: Type = next_node.type
360+
# TODO: handle in settings or move to ctx
361+
var should_translate: bool = true
360362
match next_type:
361363
Type.DIALOGUE:
362-
next_nodes.append(next_node)
364+
if should_translate:
365+
var dialogue_node_ast: ParleyDialogueNodeAst = next_node
366+
var resolved_text: String = resolve_value(ctx, dialogue_node_ast.text, dialogue_node_ast, 'text')
367+
next_nodes.append(dialogue_node_ast.to_resolved(resolved_text))
368+
else:
369+
next_nodes.append(next_node)
363370
Type.DIALOGUE_OPTION:
364-
next_nodes.append(next_node)
371+
if should_translate:
372+
var dialogue_option_node_ast: ParleyDialogueOptionNodeAst = next_node
373+
var resolved_text: String = resolve_value(ctx, dialogue_option_node_ast.text, dialogue_option_node_ast, 'text')
374+
next_nodes.append(dialogue_option_node_ast.to_resolved(resolved_text))
375+
else:
376+
next_nodes.append(next_node)
365377
Type.ACTION:
366378
if not dry_run:
367379
await _run_action(ctx, next_node)
@@ -479,12 +491,12 @@ func _evaluate_condition_node(ctx: ParleyContext, condition_node: ParleyConditio
479491
@warning_ignore("REDUNDANT_AWAIT")
480492
var result: Variant = await fact.evaluate(ctx, [])
481493
fact.free() # Previous this was call_deferred, although I'm not sure why
482-
var evaluated_value: Variant = _evaluate_value(value)
494+
var evaluated_value: Variant = resolve_value(ctx, value)
483495
match operator:
484496
ParleyConditionNodeAst.Operator.EQUAL:
485497
results.append(typeof(result) == typeof(evaluated_value) and result == evaluated_value)
486498
ParleyConditionNodeAst.Operator.NOT_EQUAL:
487-
results.append(typeof(result) != typeof(evaluated_value) or result != _evaluate_value(value))
499+
results.append(typeof(result) != typeof(evaluated_value) or result != resolve_value(ctx, value))
488500
_:
489501
if not dry_run:
490502
print_rich(ParleyUtils.log.info_msg("Operator of type %s is not supported" % [operator]))
@@ -511,20 +523,43 @@ func _evaluate_match_node(ctx: ParleyContext, match_node: ParleyMatchNodeAst) ->
511523
@warning_ignore("REDUNDANT_AWAIT")
512524
var result: Variant = await fact.evaluate(ctx, [])
513525
fact.free() # Previous this was call_deferred, although I'm not sure why
514-
var evaluated_result: Variant = _evaluate_value(result)
526+
var evaluated_result: Variant = resolve_value(ctx, result)
515527
var cases: Array = match_node.cases
516-
var case_index: int = cases.map(func(case: Variant) -> Variant: return _map_value(case)).find(evaluated_result)
528+
var case_index: int = cases.map(func(case: Variant) -> Variant: return resolve_value(ctx, case)).find(evaluated_result)
517529
if case_index == -1:
518530
return cases.find(ParleyMatchNodeAst.fallback_key)
519531
return case_index
520532

521533

522-
func _evaluate_value(value_expr: Variant) -> Variant:
523-
# TODO: add evaluation here
524-
return _map_value(value_expr)
534+
func resolve_value(ctx: ParleyContext, value_expr: Variant, node_to_translate: ParleyNodeAst = null, field_to_translate: String = &"") -> Variant:
535+
var resolved_value: Variant = value_expr
536+
if is_instance_of(value_expr, TYPE_STRING):
537+
# Resolve expressions
538+
var raw_expression: String = value_expr
539+
var value: String = _evaluate_expression(ctx, raw_expression)
540+
# Apply translations
541+
if node_to_translate:
542+
value = _translate_value(ctx, node_to_translate, field_to_translate, value)
543+
resolved_value = value
544+
545+
return _coerce_value(resolved_value)
546+
547+
548+
func _evaluate_expression(_ctx: ParleyContext, value_expr: String) -> String:
549+
# TODO: to be handled in: https://github.com/bisterix-studio/parley/pull/26
550+
return value_expr
551+
552+
553+
# TODO: add translation key to NodeAst
554+
func _translate_value(_ctx: ParleyContext, translate_node: ParleyNodeAst, translate_field: String, value: String) -> Variant:
555+
var translate_ctx: String = &""
556+
if ResourceLoader.exists(self.resource_path):
557+
var uid: String = ParleyUtils.resource.get_uid(self)
558+
translate_ctx = ParleyUtils.translation.get_msg_ctx(uid, translate_node, translate_field)
559+
return str(tr(value, translate_ctx))
525560

526561

527-
func _map_value(value_expr: Variant) -> Variant:
562+
func _coerce_value(value_expr: Variant) -> Variant:
528563
if value_expr is String and value_expr == 'true':
529564
return true
530565
if value_expr is String and value_expr == 'false':

addons/parley/parley_plugin.gd

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ extends EditorPlugin
88
const ParleyIcon: CompressedTexture2D = preload("./assets/ParleyIconBubble.svg")
99
const ParleyConstants = preload("./constants.gd")
1010
const ParleyImportPlugin: GDScript = preload("./import_plugin.gd")
11+
const ParleyTranslationParserPlugin: GDScript = preload("./translation_parser.gd")
1112
const StoresEditorScene: PackedScene = preload("./stores/stores_editor.tscn")
1213
const ParleyNodeScene: PackedScene = preload("./views/parley_node.tscn")
1314
const ParleyEdges: PackedScene = preload("./views/parley_edges.tscn")
@@ -16,6 +17,7 @@ const MainPanelScene: PackedScene = preload("./main_panel.tscn")
1617

1718
var main_panel_instance: ParleyMainPanel
1819
var import_plugin: EditorImportPlugin
20+
var translation_parser_plugin: EditorTranslationParserPlugin
1921
var stores_editor: ParleyStoresEditor
2022
var node_editor: ParleyNodeEditor
2123
var edges_editor: ParleyEdgesEditor
@@ -38,6 +40,10 @@ func _enter_tree() -> void:
3840
# Import plugin setup
3941
import_plugin = ParleyImportPlugin.new()
4042
add_import_plugin(import_plugin)
43+
44+
# Translation plugin setup
45+
translation_parser_plugin = ParleyTranslationParserPlugin.new()
46+
add_translation_parser_plugin(translation_parser_plugin)
4147

4248
# Stores Editor Dock
4349
stores_editor = StoresEditorScene.instantiate()
@@ -91,6 +97,10 @@ func _exit_tree() -> void:
9197
if import_plugin:
9298
remove_import_plugin(import_plugin)
9399
import_plugin = null
100+
101+
if translation_parser_plugin:
102+
remove_translation_parser_plugin(translation_parser_plugin)
103+
import_plugin = null
94104

95105
if node_editor:
96106
remove_control_from_docks(node_editor)

addons/parley/stores/character/character_store.gd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func get_character_by_ref(character_ref: String) -> ParleyCharacter:
8787
return ParleyCharacter.new("", "Unknown")
8888

8989

90+
# TODO: add translation support here
9091
static func resolve_character_ref(character_ref: String) -> ParleyCharacter:
9192
var parts: PackedStringArray = character_ref.split('::')
9293
if parts.size() == 0 or not ResourceLoader.exists(parts[0]):
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
@tool
2+
extends EditorTranslationParserPlugin
3+
4+
5+
func _parse_file(path: String) -> Array[PackedStringArray]:
6+
if not ResourceLoader.exists(path):
7+
return []
8+
var dialogue_sequence: ParleyDialogueSequenceAst = ResourceLoader.load(path, 'ParleyDialogueSequenceAst')
9+
var ret: Array[PackedStringArray] = []
10+
var uid: String = ParleyUtils.resource.get_uid(dialogue_sequence)
11+
for node: ParleyNodeAst in dialogue_sequence.nodes:
12+
if not uid:
13+
continue
14+
# TODO: handle this at the node level to be much more maintainable
15+
var text: Variant = node.get("text")
16+
if is_instance_of(text, TYPE_STRING) and text:
17+
ret.append(PackedStringArray([text, ParleyUtils.translation.get_msg_ctx(uid, node, 'text')])) # id,ctx
18+
return ret
19+
20+
21+
func _get_recognized_extensions() -> PackedStringArray:
22+
# TODO: is there a constant we can use?
23+
return ["ds"]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://dim2vkndvbtcp

addons/parley/utils/parley_util.gd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,8 @@ class file:
9292
static func _emit_filesystem_changed(timeout: Signal) -> void:
9393
EditorInterface.get_resource_filesystem().filesystem_changed.emit()
9494
signals.safe_disconnect(timeout, _emit_filesystem_changed)
95+
96+
97+
class translation:
98+
static func get_msg_ctx(uid: String, node: ParleyNodeAst, field: String) -> String:
99+
return "%s::%s::%s" % [uid, node.id, field]

locale/all_ds.pot

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# LANGUAGE translation for Parley for the following files:
2+
# res://dialogue_sequences/all.ds
3+
#
4+
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5+
#
6+
#, fuzzy
7+
msgid ""
8+
msgstr ""
9+
"Project-Id-Version: Parley\n"
10+
"MIME-Version: 1.0\n"
11+
"Content-Type: text/plain; charset=UTF-8\n"
12+
"Content-Transfer-Encoding: 8-bit\n"
13+
14+
#: dialogue_sequences/all.ds
15+
msgctxt "uid://d2883lul36r1d::node:3::text"
16+
msgid "Look. I made a whatdyamacallit."
17+
msgstr ""
18+
19+
#: dialogue_sequences/all.ds
20+
msgctxt "uid://d2883lul36r1d::node:4::text"
21+
msgid "Wow. Great."
22+
msgstr ""
23+
24+
#: dialogue_sequences/all.ds
25+
msgctxt "uid://d2883lul36r1d::node:7::text"
26+
msgid "I have a coffee."
27+
msgstr ""
28+
29+
#: dialogue_sequences/all.ds
30+
msgctxt "uid://d2883lul36r1d::node:8::text"
31+
msgid "Give to Alice."
32+
msgstr ""
33+
34+
#: dialogue_sequences/all.ds
35+
msgctxt "uid://d2883lul36r1d::node:13::text"
36+
msgid "Keep it."
37+
msgstr ""
38+
39+
#: dialogue_sequences/all.ds
40+
msgctxt "uid://d2883lul36r1d::node:10::text"
41+
msgid "Here you go."
42+
msgstr ""
43+
44+
#: dialogue_sequences/all.ds
45+
msgctxt "uid://d2883lul36r1d::node:11::text"
46+
msgid "Cheers."
47+
msgstr ""
48+
49+
#: dialogue_sequences/all.ds
50+
msgctxt "uid://d2883lul36r1d::node:12::text"
51+
msgid "You're welcome."
52+
msgstr ""
53+
54+
#: dialogue_sequences/all.ds
55+
msgctxt "uid://d2883lul36r1d::node:14::text"
56+
msgid "But it's mine."
57+
msgstr ""
58+
59+
#: dialogue_sequences/all.ds
60+
msgctxt "uid://d2883lul36r1d::node:15::text"
61+
msgid "Fair enough."
62+
msgstr ""
63+
64+
#: dialogue_sequences/all.ds
65+
msgctxt "uid://d2883lul36r1d::node:17::text"
66+
msgid "Mmm. Coffee. Now, what did you want me to look at?"
67+
msgstr ""
68+
69+
#: dialogue_sequences/all.ds
70+
msgctxt "uid://d2883lul36r1d::node:19::text"
71+
msgid "I NEED COFFEE!"
72+
msgstr ""
73+
74+
#: dialogue_sequences/all.ds
75+
msgctxt "uid://d2883lul36r1d::node:20::text"
76+
msgid "I'll get you coffee, Mama. But first, CATNIP."
77+
msgstr ""
78+
79+
#: dialogue_sequences/all.ds
80+
msgctxt "uid://d2883lul36r1d::node:24::text"
81+
msgid "Look, I need coffee."
82+
msgstr ""
83+
84+
#: dialogue_sequences/all.ds
85+
msgctxt "uid://d2883lul36r1d::node:25::text"
86+
msgid "Look, I really need coffee."
87+
msgstr ""
88+
89+
#: dialogue_sequences/all.ds
90+
msgctxt "uid://d2883lul36r1d::node:26::text"
91+
msgid "Look, I really really REALLY need coffee."
92+
msgstr ""
93+
94+
#: dialogue_sequences/all.ds
95+
msgctxt "uid://d2883lul36r1d::node:27::text"
96+
msgid "Coffee please."
97+
msgstr ""

0 commit comments

Comments
 (0)