Skip to main content
View
Mystery Gift — Step 8 / 11Add i18n text
1. Prepare the assets and text before creating a UI
2. Create a UI scene
3. Create a Composition
4. Create a SpriteStack component
5. Add a data layer with PFM
6. Handle keyboard input
7. Handle mouse input
8. Add i18n text
9. Use confirmation dialogs
10. Create animations
11. Customize GenericBase

Add i18n text

This guide builds on the mouse guide. The setup guide already installed the CSV file, and we declared the TEXT_* constants back in the composition guide. Here we explain how the code reads that CSV, add a helper to keep the calls short, and replace our last hardcoded string.

Principle

All player-visible text must come from a CSV file -- never hardcode strings in Ruby. The PSDK i18n system picks the column matching the game's current language:

  • Each CSV file has a unique numeric identifier (TEXT_FILE_ID).
  • Each row maps to an index: the first data row is ID 0, the next is ID 1, and so on.
  • Empty cells fall back to the English (en) value, so an unfinished translation still shows English.
  • You read a row with ext_text(file_id, row_index).

The CSV and its constants

The CSV lives at Data/Text/Dialogs/311125.csv (installed in the setup guide). Its filename matches TEXT_FILE_ID = 311_125. Each row maps to a TEXT_* constant from 001 Constants.rb:

ConstantRowEnglish text
TEXT_ENTER_CODE0Enter Code
TEXT_QUIT1Quit
TEXT_TITLE2Mystery Gift
TEXT_PROMPT3Enter your gift code
TEXT_INVALID4Invalid code!
TEXT_ALREADY_CLAIMED5Gift already claimed!
TEXT_RECEIVED6You received %s!
TEXT_NO_GIFTS7No gifts claimed yet
TEXT_CONFIRM8Claim this gift?
TEXT_GIFT_RECEIVED9Gift received!
TEXT_YES10Yes
TEXT_NO11No

We already use two of these: the Composition reads ext_text(TEXT_FILE_ID, TEXT_TITLE) for the title and TEXT_NO_GIFTS for the empty message. The rest are used as we build the dialogs.

A helper to shorten the calls

Writing ext_text(TEXT_FILE_ID, id) everywhere is verbose. Add a private helper to the scene. Update scripts/20 MysteryGift/003 GamePlay/001 Main.rb -- add this method (the include UI::MysteryGift at the top of the class makes the TEXT_* constants available):

# Shortcut to access Mystery Gift text
# @param id [Integer] the text row index
# @return [String]
def gift_text(id)
return ext_text(TEXT_FILE_ID, id)
end
  • gift_text(TEXT_QUIT) is much clearer than ext_text(311_125, 1).
  • It returns the text already in the game's current language.

The Composition is in the UI::MysteryGift module and calls ext_text directly; the scene uses this gift_text shortcut. Both reach the same CSV.

Localizing the buttons

In the scene guide we hardcoded 'Quit' in button_texts. Replace it with the CSV value. Update button_texts in 001 Main.rb:

# Return the button texts for the ctrl buttons [A, X, Y, B]
# @return [Array<String, nil>]
def button_texts
return [nil, nil, nil, gift_text(TEXT_QUIT)]
end
  • The B button now reads its label from the CSV: "Quit" in English, "Quitter" in French.
  • The A button stays hidden (nil) for now. We reveal it -- with its gift_text(TEXT_ENTER_CODE) label -- in the dialogs guide, at the same time as the action_a it triggers. (A visible ctrl button calls its action on click, so it must not appear before its action exists.)

Text colors

PSDK uses an indexed color palette:

add_text(0, 0, 200, 18, gift_text(TEXT_TITLE), color: 0) # black
add_text(0, 0, 200, 18, gift_text(TEXT_TITLE), color: 1) # blue
add_text(0, 0, 200, 18, gift_text(TEXT_TITLE), color: 2) # red
add_text(0, 0, 200, 18, gift_text(TEXT_TITLE), color: 3) # green
add_text(0, 0, 200, 18, gift_text(TEXT_TITLE), color: 9) # grey
add_text(0, 0, 200, 18, gift_text(TEXT_TITLE), color: 10) # white
  • The color: parameter is an index into the font palette. Common ones: 0 black, 1 blue, 2 red, 3 green, 9 grey, 10 white.

For an exact RGB color not in the palette, omit color: and assign fill_color/outline_color directly:

title = add_text(0, 0, 200, 18, gift_text(TEXT_TITLE))
title.fill_color = Color.new(0, 255, 136)
title.outline_color = Color.new(0, 85, 45)
  • A common trick for the outline is to divide each fill channel by 3: a fill of (0, 255, 136) pairs with an outline of (0, 85, 45).

Dynamic text with format

When a row contains a format specifier (%s for a string, %d for an integer), Ruby's format fills it in:

# "You received Rare Candy!" / "Vous avez recu Rare Candy !"
message = format(gift_text(TEXT_RECEIVED), gift_name)
  • gift_text(TEXT_RECEIVED) returns "You received %s!"; format(template, gift_name) replaces %s. We use this in the dialogs guide to announce the claimed gift.
  • The gift name comes from the database, which is not translated here.

Try it

Switch the game to French (or check the B button): the bottom button now reads "Quitter" instead of a hardcoded "Quit", and the title stays localized.

Conclusion

  • All player-visible text goes through a CSV identified by TEXT_FILE_ID; read it with ext_text(file_id, row_index).
  • The TEXT_* constants (declared in 001 Constants.rb) name the CSV rows -- no magic numbers.
  • Add a gift_text helper in the scene to shorten ext_text calls; the Composition uses ext_text directly.
  • Never hardcode player-facing strings -- we replaced the last one ('Quit').
  • Use indexed color: for palette colors, or fill_color/outline_color for exact RGB.
  • Use format with %s/%d for parameterized text.