Improved internationalization support

This commit is contained in:
Mathew Velasquez 2016-04-06 13:45:36 -04:00
parent d0cb19e9d2
commit d53ad4b124
17 changed files with 1823 additions and 344 deletions

1476
binding-mri/module_rpg1.rb Normal file

File diff suppressed because it is too large Load diff

View file

@ -235,7 +235,7 @@ class Interpreter
#--------------------------------------------------------------------------
def setup_choices(parameters)
# Set choices
$game_temp.choices = parameters[0].map{|s| $tr.event(@event_name, s.strip)}
$game_temp.choices = parameters[0].map{|s| Language.tr(s.strip)}
# Set cancel processing
$game_temp.choice_cancel_type = parameters[1]
# Set callback

View file

@ -45,7 +45,7 @@ class Interpreter
text.strip!
# Translate text
text = $tr.event(@event_name, text).clone
text = Language.tr(text).clone
# Parse first line for portrait specifier
if text[0,1] == '@'

View file

@ -11,6 +11,7 @@ begin
# Load persistent data
Persistent.load
$persistent.lang = 'ja'
# Prepare for transition
Graphics.freeze

View file

@ -28,11 +28,15 @@ class Persistent
# Members
def lang=(val)
@lang = val
$tr = Translator.new(@lang)
$lang = Language.get(@lang)
Font.default_name = $lang.font
Oneshot.set_yes_no(tr('Yes'), tr('No'))
case val
when String
@lang = LanguageCode.new(val)
when LanguageCode
@lang = val
else
raise 'value passed to Persistent.lang neither String nor LanguageCode'
end
Language.set(@lang)
end
# MARSHAL

View file

@ -11,7 +11,7 @@ class Scene_Name
def main
# Make windows
@edit_window = Window_NameEdit.new
@input_window = Window_NameInput.new
@input_window = Language.create_input
# Execute transition
Graphics.transition
# Main loop
@ -56,26 +56,35 @@ class Scene_Name
if Input.trigger?(Input::ACTION)
# If cursor position is at [OK]
if @input_window.character == nil
# If name is empty
if @edit_window.name == ""
# Return to default name
@edit_window.restore_default
# Cursor is at OK or mode buttons
slot = @input_window.mode_button_slot
if slot == -1
# Cursor is at OK
# If name is empty
if @edit_window.name == ""
# Play buzzer SE
$game_system.se_play($data_system.buzzer_se)
# Return to default name
@edit_window.restore_default
# If name is empty
if @edit_window.name == ""
# Play buzzer SE
$game_system.se_play($data_system.buzzer_se)
return
end
# Play decision SE
$game_system.se_play($data_system.decision_se)
return
end
# Change actor name
$game_oneshot.player_name = @edit_window.name
# Play decision SE
$game_system.se_play($data_system.decision_se)
# Switch to map screen
$scene = Scene_Map.new
return
end
# Change actor name
$game_oneshot.player_name = @edit_window.name
# Play decision SE
# Cursor is on mode buttons
$game_system.se_play($data_system.decision_se)
# Switch to map screen
$scene = Scene_Map.new
@input_window.cycle_mode(slot)
return
end
# If cursor position is at maximum

View file

@ -26,6 +26,7 @@ class Scene_Title
$data_tilesets = load_data("Data/Tilesets.rxdata")
$data_common_events = load_data("Data/CommonEvents.rxdata")
$data_system = load_data("Data/System.rxdata")
Language.initialize_database
# Load save game/initialize data
$game_temp = Game_Temp.new
new_game unless load

View file

@ -117,7 +117,7 @@ end
# Map specific settings
def green_ambient
ambient -50, -50, -50
ambient -40, -40, -40
end
# Travel

View file

@ -12,7 +12,6 @@ class Sprite_Timer < Sprite
def initialize
super
self.bitmap = Bitmap.new(88, 48)
self.bitmap.font.name = "Terminus (TTF)"
self.bitmap.font.size = 32
self.x = 640 - self.bitmap.width
self.y = 0

View file

@ -87,7 +87,6 @@ class Window_Message < Window_Selectable
# Initialize
@blip = BLIP_TIME
@text = ''
y = -1
# Pre-process text
if $game_temp.message_text != nil && !$game_temp.message_text.empty?
@ -140,6 +139,8 @@ class Window_Message < Window_Selectable
break if y >= 4
end
end
@text.rstrip!
lines = @text.empty? ? 0 : @text.count("\n") + 1
# Prepare renderer
self.contents.clear
@ -154,8 +155,8 @@ class Window_Message < Window_Selectable
if $game_temp.choices != nil
# Prepare choices, if they fit
if $game_temp.choices.size + y < 4
@choice_start = y + 1
if lines + $game_temp.choices.size <= 4
@choice_start = lines
@item_max = $game_temp.choices.size
else
# Don't call the message callback till we can show all the choices
@ -163,8 +164,8 @@ class Window_Message < Window_Selectable
end
elsif $game_temp.num_input_variable_id > 0
# Prepare number input, if it fits
if y < 3
@number_start = y + 1
if lines < 3
@number_start = lines
else
# Don't call the message callback till we get a number
@skip_message_proc = true

View file

@ -4,8 +4,38 @@
# This window is used to select text characters on the input name screen.
#==============================================================================
class NameInputMode
attr_reader :names
attr_reader :size
attr_accessor :index
attr_accessor :count
def initialize(names, bitmap)
@names = names
@size = names.map { |n| bitmap.text_size(n).width }.max
@index = 0
@count = 0
end
def cycle
@index = (@index + 1) % @count
end
def label
@names[(@index + 1) % @count]
end
end
class Window_NameInput < Window_Base
GROUP_WIDTH = 5
BUTTON_BUFFER = 8
BUTTON_PADDING = 16
attr_accessor :index
attr_accessor :ok_text
attr_accessor :character_tables
attr_accessor :table_height
attr_accessor :char_w
attr_accessor :char_h
#--------------------------------------------------------------------------
# * Object Initialization
@ -14,51 +44,123 @@ class Window_NameInput < Window_Base
super(0, 128, 640, 352)
self.contents = Bitmap.new(width - 32, height - 32)
@index = 0
@ok_text = "OK"
@char_w = 28
@char_h = 32
end
def init
# Create dimension information
@character_table = $lang.character_table
@group_height = $lang.character_table_height
@group_size = @group_height * GROUP_WIDTH
@num_groups = @character_table.length / @group_size
@start_x = ((width - 32) - @num_groups * 152 + 12) / 2
@ok_text = tr("OK")
@table_size = @character_tables.values.first.size
@table_width = @table_size / @table_height
@start_x = ((width - 32) - @table_width * @char_w) / 2
@start_y = ((height - 32) - (@table_height + 1) * @char_h + BUTTON_BUFFER) / 2
@ok_text_size = self.contents.text_size(@ok_text).width
# Initialize mode information
@character_tables.each_key do |key|
key.each_with_index do |new_max, i|
@modes[i].count = new_max + 1 if @modes[i].count < new_max + 1
end
end
refresh
update_cursor_rect
self
end
#--------------------------------------------------------------------------
# * Properties
#--------------------------------------------------------------------------
def set_mode_buttons(buttons)
@modes = buttons.map { |n| NameInputMode.new(n, self.contents) }
end
#--------------------------------------------------------------------------
# * Text Character Acquisition
#--------------------------------------------------------------------------
def character
return @character_table[@index]
return current_table[@index]
end
#--------------------------------------------------------------------------
# * Mode Button Cursor Positioning
#--------------------------------------------------------------------------
def mode_button_slot
pos = @index % @table_width
x = 0
@modes.each_with_index do |mode, i|
size = mode.size + BUTTON_PADDING * 2
if pos * @char_w >= x && pos * @char_w <= x + size
if (pos + 1) * @char_w - BUTTON_PADDING <= x + size || i < @modes.size - 1
return i
end
end
x += size
end
return -1
end
#--------------------------------------------------------------------------
# * Cycle Current Mode
#--------------------------------------------------------------------------
def cycle_mode(slot)
@modes[slot].cycle
refresh
end
#--------------------------------------------------------------------------
# * Get Character Table for Mode
#--------------------------------------------------------------------------
def current_table
@character_tables[@modes.map { |m| m.index }]
end
#--------------------------------------------------------------------------
# * Refresh
#--------------------------------------------------------------------------
def refresh
self.contents.clear
for i in 0...@character_table.length
x = @start_x + i / GROUP_WIDTH / @group_height * 152 + i % GROUP_WIDTH * 28
y = i / GROUP_WIDTH % @group_height * 32
self.contents.draw_text(x, y, 28, 32, @character_table[i], 1)
table = current_table
@table_size.times do |i|
x = @start_x + (i % @table_width) * @char_w
y = @start_y + (i / @table_width) * @char_h
self.contents.draw_text(x, y, @char_w, @char_h, table[i], 1)
end
# OK button
self.contents.draw_text(@start_x + @table_width * @char_w - @ok_text_size - BUTTON_PADDING,
@start_y + @table_height * @char_h + BUTTON_BUFFER,
@ok_text_size, @char_h, @ok_text, 1)
# Mode buttons
x = @start_x + BUTTON_PADDING
@modes.each do |mode|
self.contents.draw_text(x, @start_y + @table_height * @char_h + BUTTON_BUFFER,
mode.size, @char_h, mode.label, 1)
x += BUTTON_PADDING * 2 + mode.size
end
self.contents.draw_text(@start_x + @num_groups * 152 - 12 - @ok_text_size - 16,
9 * 32, @ok_text_size, 32, @ok_text, 1)
end
#--------------------------------------------------------------------------
# * Cursor Rectangle Update
#--------------------------------------------------------------------------
def update_cursor_rect
# If cursor is positioned on [OK]
if @index >= @character_table.length
self.cursor_rect.set(@start_x + @num_groups * 152 - 12 - @ok_text_size - 32,
9 * 32, @ok_text_size + 32, 32)
# If cursor is positioned on anything other than [OK]
if @index >= @table_size
# Cursor is positioned on OK or mode buttons
slot = mode_button_slot
if slot >= 0
# Cursor is positioned on mode button
x = @start_x
@modes.each_with_index do |mode, i|
if slot == i
self.cursor_rect.set(x, @start_y + @table_height * @char_h + BUTTON_BUFFER,
mode.size + BUTTON_PADDING * 2, @char_h)
break
end
x += BUTTON_PADDING * 2 + mode.size
end
else
# Cursor is positioned on OK button
self.cursor_rect.set(@start_x + @table_width * @char_w - @ok_text_size - BUTTON_PADDING * 2,
@start_y + @table_height * @char_h + BUTTON_BUFFER,
@ok_text_size + 32, @char_h)
end
else
x = @start_x + @index / GROUP_WIDTH / @group_height * 152 + @index % GROUP_WIDTH * 28
y = @index / GROUP_WIDTH % @group_height * 32
self.cursor_rect.set(x, y, 28, 32)
# Cursor is not positioned on OK or mode buttons
x = @start_x + (@index % @table_width) * @char_w
y = @start_y + (@index / @table_width) * @char_h
self.cursor_rect.set(x, y, @char_w, @char_h)
end
end
#--------------------------------------------------------------------------
@ -66,77 +168,87 @@ class Window_NameInput < Window_Base
#--------------------------------------------------------------------------
def update
super
# If cursor is positioned on [OK]
if @index >= @character_table.length
# Cursor down
if Input.trigger?(Input::DOWN)
$game_system.se_play($data_system.cursor_se)
@index -= @character_table.length
end
# Cursor up
if Input.repeat?(Input::UP)
$game_system.se_play($data_system.cursor_se)
@index -= @character_table.length - (@group_size - GROUP_WIDTH)
end
# If cursor is positioned on anything other than [OK]
else
# If right directional button is pushed
if Input.repeat?(Input::RIGHT)
# If directional button pressed down is not a repeat, or
# cursor is not positioned on the right edge
if Input.trigger?(Input::RIGHT) or
@index / @group_size < 3 or @index % GROUP_WIDTH < 4
# Move cursor to right
$game_system.se_play($data_system.cursor_se)
if @index % GROUP_WIDTH < 4
@index += 1
else
@index += @group_size - 4
end
if @index >= @character_table.length
@index -= @character_table.length
end
end
end
# If left directional button is pushed
if @index >= @table_size
# Cursor is positioned on OK or mode buttons
if Input.repeat?(Input::LEFT)
# If directional button pressed down is not a repeat, or
# cursor is not positioned on the left edge
if Input.trigger?(Input::LEFT) or
@index / @group_size > 0 or @index % GROUP_WIDTH > 0
# Move cursor to left
old_slot = mode_button_slot
if Input.trigger?(Input::LEFT) || old_slot != 0
$game_system.se_play($data_system.cursor_se)
if @index % GROUP_WIDTH > 0
if old_slot == 0
@index = @table_size + @table_width - 1
else
loop do
@index -= 1
break if old_slot != mode_button_slot
end
end
end
end
if Input.repeat?(Input::RIGHT)
# If directional button pressed down is not a repeat, or
# cursor is not positioned on the right edge
old_slot = mode_button_slot
if Input.trigger?(Input::RIGHT) || old_slot != -1
$game_system.se_play($data_system.cursor_se)
if old_slot == -1
@index = @table_size
else
loop do
@index += 1
break if old_slot != mode_button_slot
end
end
end
end
if Input.trigger?(Input::DOWN)
$game_system.se_play($data_system.cursor_se)
@index -= @table_size
end
if Input.repeat?(Input::UP)
$game_system.se_play($data_system.cursor_se)
@index -= @table_width
end
else
# Cursor is not positioned on OK or mode buttons
if Input.repeat?(Input::RIGHT)
# If directional button pressed down is not a repeat, or
# cursor is not positioned on the right edge
if Input.trigger?(Input::RIGHT) || @index % @table_width < @table_width - 1
$game_system.se_play($data_system.cursor_se)
if @index % @table_width < @table_width - 1
@index += 1
else
@index -= @table_width - 1
end
end
end
if Input.repeat?(Input::LEFT)
# If directional button pressed down is not a repeat, or
# cursor is not positioned on the left edge
if Input.trigger?(Input::LEFT) || @index % @table_width > 0
$game_system.se_play($data_system.cursor_se)
if @index % @table_width > 0
@index -= 1
else
@index -= @group_size - 4
end
if @index < 0
@index += @character_table.length
@index += @table_width - 1
end
end
end
# If down directional button is pushed
if Input.repeat?(Input::DOWN)
# Move cursor down
$game_system.se_play($data_system.cursor_se)
if @index % @group_size < @group_size - GROUP_WIDTH
@index += GROUP_WIDTH
else
@index += @character_table.length - (@group_size - GROUP_WIDTH)
end
@index += @table_width
end
# If up directional button is pushed
if Input.repeat?(Input::UP)
# If directional button pressed down is not a repeat, or
# cursor is not positioned on the upper edge
if Input.trigger?(Input::UP) or @index % @group_size >= GROUP_WIDTH
# Move cursor up
if Input.trigger?(Input::UP) || @index >= @table_width
$game_system.se_play($data_system.cursor_se)
if @index % @group_size >= GROUP_WIDTH
@index -= GROUP_WIDTH
if @index >= @table_width
@index -= @table_width
else
@index += @character_table.length
@index += @table_size
end
end
end

View file

@ -100,8 +100,8 @@ Scene_Debug
Persistent
# Internationalization
i18n_Translator
i18n_Language
i18n_English
# Data
Data_Item

27
scripts/i18n_English.rb Normal file
View file

@ -0,0 +1,27 @@
# Default English localization settings
Font.default_name = Language::FONT_WESTERN
class Language
def self.create_input
input = Window_NameInput.new
input.set_mode_buttons [['Upper', 'Lower']]
input.character_tables = {
[0] => [ # Case: Upper
"A","B","C","D","E","F","G","H","I","J",
"K","L","M","N","O","P","Q","R","S","T",
"U","V","W","X","Y","Z"," ",".",",","-",
"0","1","2","3","4","5","6","7","8","9",
"!","?",'"',"'","/","&","[","]","(",")",
],
[1] => [ # Case: Lower
"a","b","c","d","e","f","g","h","i","j",
"k","l","m","n","o","p","q","r","s","t",
"u","v","w","x","y","z"," ",".",",","-",
"0","1","2","3","4","5","6","7","8","9",
"!","?",'"',"'","/","&","[","]","(",")",
],
}
input.table_height = 5
input.init
end
end

35
scripts/i18n_Japanese.rb Normal file
View file

@ -0,0 +1,35 @@
Font.default_name = Language::FONT_CJK
class Language
def self.create_input
input = Window_NameInput.new
input.ok_text = '決定'
input.set_mode_buttons [['かな', 'カナ', '英字']]
input.character_tables = {
[0] => [ # Hiragana
"","","","","","","","","","","","","","","","","","",
"","","","","","","",""," ",""," ","","","","","","","",
"","","","","","","","","","","","","","","","","","",
"","","","","","","",""," ",""," ","","","","","","","",
"","","","","","","","","","","","","","","","","","",
],
[1] => [ # Katakana
"","","","","","","","","","","","","","","","","","",
"","","","","","","",""," ",""," ","","","","","","","",
"","","","","","","","","","","","","","","","","","",
"","","","","","","",""," ",""," ","","","","","","","",
"","","","","","","","","","","","","","","","","","",
],
[2] => [ # Latin
"A","B","C","D","E","F","G","H","I","a","b","c","d","e","f","g","h","i",
"J","K","L","M","N","O","P","Q","R","j","k","l","m","n","o","p","q","r",
"S","T","U","V","W","X","Y","Z"," ","s","t","u","v","w","x","y","z"," ",
"0","1","2","3","4","+","-","=","&","!","?","'",'"',".",",",":",";","$",
"5","6","7","8","9","*","/","#","|","[","]","(",")","<",">","{","}","@",
],
}
input.index = 17 # Start on あ
input.table_height = 5
input.init
end
end

View file

@ -1,147 +1,61 @@
# Classes for translating text similar to GNU gettext
# Translator class: translate text to another language
class Language
attr_reader :font
attr_reader :character_table
attr_reader :character_table_height
def initialize(font, character_table, character_table_height)
@font = font
@character_table = character_table
@character_table_height = character_table_height
end
def self.get(lc)
return LANGUAGES[lc.full] || LANGUAGES[lc.lang] || LANGUAGES[:en]
end
FONT_WESTERN = 'Terminus (TTF)'
FONT_CJK = 'WenQuanYi Micro Hei'
LANGUAGES = {
:en => Language.new(
FONT_WESTERN, [
"A","B","C","D","E",
"F","G","H","I","J",
"K","L","M","N","O",
"P","Q","R","S","T",
"U","V","W","X","Y",
"Z"," "," "," "," ",
"+","-","*","/","!",
"1","2","3","4","5",
class << self
def set(lc)
@tr = nil
script = nil
[lc.full.to_s, lc.lang.to_s].each do |name|
begin
@tr, script = load_data("Languages/#{name}.rxdata")
break
rescue
end
end
exec(script) if script
Oneshot.set_yes_no(tr('Yes'), tr('No'))
end
"a","b","c","d","e",
"f","g","h","i","j",
"k","l","m","n","o",
"p","q","r","s","t",
"u","v","w","x","y",
"z"," "," "," "," ",
"#","$","%","&","@",
"6","7","8","9","0",
], 8
),
:es => Language.new(
FONT_WESTERN, [
"A","B","C","D","E",
"F","G","H","I","J",
"K","L","M","N","O",
"P","Q","R","S","T",
"U","V","W","X","Y",
"Z"," ","Ñ","Ü"," ",
"Á","É","Í","Ó","Ú",
"+","-","*","/","!",
"1","2","3","4","5",
# Translate some text
def tr(string)
if @data
@data[Zlib::crc32(string)] || string
else
string
end
end
"a","b","c","d","e",
"f","g","h","i","j",
"k","l","m","n","o",
"p","q","r","s","t",
"u","v","w","x","y",
"z"," ","ñ","ü"," ",
"á","é","í","ó","ú",
"¡","#","%","&","@",
"6","7","8","9","0",
], 9
),
:ja => Language.new(
FONT_CJK, [
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"", "","", "","",
"","","","","",
"", "","", "","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","", "", "", "",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"", "","", "","",
"","","","","",
"", "","", "","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","", "", "",
], 9
),
:ko => Language.new(
FONT_CJK, [
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
"","","","","",
], 8
),
}
# Turn all database items into translatable strings
def initialize_database
$data_actors.each do |i|
i.name = TrString.new(i.name) if i && !i.name.empty?
end
$data_items.each do |i|
if i && !i.name.empty?
i.name = TrString.new(i.name)
i.description = TrString.new(i.description)
end
end
end
end
end
# Translatable string
class TrString
def initialize(str)
@str = str
end
def to_str
Language.tr(@str)
end
alias :to_s :to_str
end
def tr(text)
TrString.new(text)
end

View file

@ -1,87 +0,0 @@
# Classes for translating text similar to GNU gettext
# Translation class: contains translation data
class Translation
attr_reader :events
attr_reader :scripts
attr_reader :actors
attr_reader :items
end
# Translator class: translate text to another language
class Translator
# Create Translator object
def initialize(lc)
@data = nil
[lc.full.to_s, lc.lang.to_s].each do |name|
begin
@data = load_data("Langauges/#{name}.rxdata")
break
rescue
end
end
end
# Translate all database items
def translate_database
$data_actors.each do |i|
i.name = actor(i.name) if i && !i.name.empty?
end
$data_items.each do |i|
i.name, i.description = item(i.name, i.description) if i && !i.name.empty?
end
end
# Translate script text
def script(name, text)
if @data == nil
return text
else
return @data.scripts["#{name}/#{text}"] || text
end
end
# Translate text from event
def event(name, text)
if @data == nil
return text
else
return @data.events["#{name}/#{text}"] || text
end
end
private
# Translate Actor name
def actor(name)
if @data == nil
return name
else
return @data.actors[name] || name
end
end
# Translate Item name (and description)
def item(name, description)
if @data == nil
return [name, description]
else
return @data.items[name] || [name, description]
end
end
end
# Translatable string
class TrString
def initialize(str)
@str = str
end
def to_str
@str
end
alias :to_s :to_str
end
def tr(text)
TrString.new(text)
end

View file

@ -49,19 +49,6 @@ SharedState *SharedState::instance = 0;
int SharedState::rgssVersion = 0;
static GlobalIBO *_globalIBO = 0;
static const char *gameArchExt()
{
if (rgssVer == 1)
return ".rgssad";
else if (rgssVer == 2)
return ".rgss2a";
else if (rgssVer == 3)
return ".rgss3a";
assert(!"unreachable");
return 0;
}
struct SharedStatePrivate
{
void *bindingData;