Voltar para o Blog
Quest Log

Como Traduzir e Localizar um Jogo no Godot: CSV, tr() e Troca de Idioma

Ilustração de um globo conectando idiomas diferentes em um jogo feito no Godot

Aprenda localização no Godot na prática: monte o CSV de tradução, use tr() no código e troque o idioma do jogo em runtime sem reiniciar nada.

Como Traduzir e Localizar um Jogo no Godot: CSV, tr() e Troca de Idioma

Lançar um jogo só em português é deixar a maior parte do mercado na mesa. A boa notícia: localização no Godot é um dos sistemas mais simples da engine. Você escreve as traduções num CSV, a engine importa, e o texto certo aparece no idioma certo. Sem plugin, sem biblioteca externa, sem gambiarra.

A má notícia é que quase todo mundo descobre isso tarde demais, com o jogo inteiro cheio de texto chumbado direto nos nodes e nos scripts. Aí a localização vira uma caçada manual por centenas de strings espalhadas.

Esse tutorial cobre o fluxo completo no Godot 4: montar o arquivo CSV de tradução, registrar no projeto, usar tr() no código, traduzir a UI automaticamente e trocar o idioma em runtime com a escolha salva em disco. Se você seguir isso desde o começo do projeto, traduzir pra um idioma novo vira tarefa de uma tarde.

Como funciona a localização no Godot

O sistema gira em torno de uma ideia: o jogo nunca exibe texto, exibe chaves. Em vez de escrever "Novo Jogo" num botão, você escreve MENU_NEW_GAME. Na hora de renderizar, a engine pergunta ao TranslationServer: "qual é o texto de MENU_NEW_GAME no idioma atual?". O TranslationServer responde com a string da tabela de tradução carregada.

O Godot aceita dois formatos de tabela:

  • CSV: uma planilha simples, uma linha por chave, uma coluna por idioma. É o formato que eu recomendo pra maioria dos projetos indie: qualquer pessoa edita no Google Sheets ou LibreOffice, inclusive o tradutor que você contratar.
  • gettext (PO/POT): o formato clássico de localização de software. Vale a pena quando o projeto cresce, porque tem ferramentas maduras (Poedit, Weblate) e suporte a plural por idioma. Pra começar, é peso desnecessário.

Vou seguir com CSV, que cobre o caso de uso de 90% dos jogos indie.

Criando o CSV de tradução

Crie um arquivo text.csv na pasta do projeto (eu uso res://locale/text.csv). A primeira coluna se chama obrigatoriamente keys, e cada coluna seguinte recebe o código do idioma:

keys,en,pt_BR,es
MENU_NEW_GAME,New Game,Novo Jogo,Nuevo Juego
MENU_CONTINUE,Continue,Continuar,Continuar
MENU_OPTIONS,Options,Opções,Opciones
MENU_QUIT,Quit,Sair,Salir
HUD_LIVES,Lives,Vidas,Vidas
DIALOG_GREETING,"Hello, {name}!","Olá, {name}!","¡Hola, {name}!"

Detalhes que evitam dor de cabeça:

  • Códigos de idioma usam underscore: pt_BR, en, es, fr. É o padrão de locale que a engine espera.
  • Salve em UTF-8. Se os acentos aparecerem quebrados no jogo, é quase certeza que o arquivo foi salvo em outro encoding.
  • Texto com vírgula vai entre aspas, como em qualquer CSV (veja a linha do DIALOG_GREETING).
  • Padronize as chaves por contexto: MENU_, HUD_, DIALOG_, ITEM_. Quando o CSV passar de 500 linhas, você vai agradecer.

Ao salvar o arquivo dentro do projeto, o Godot importa automaticamente como tradução e gera um arquivo .translation por idioma: text.en.translation, text.pt_BR.translation e assim por diante. Se você usa CSV pra outra coisa no projeto (dados de itens, por exemplo), selecione esse outro arquivo no FileSystem e, na aba Import, mude o tipo pra Keep File pra ele não virar tradução por engano.

Registrando as traduções no projeto

Importar não basta, você precisa registrar:

  1. Abra Project > Project Settings > Localization > Translations
  2. Clique em Add e selecione os arquivos .translation gerados (não o CSV)
  3. Em Internationalization > Locale, confira o Fallback, que é o idioma usado quando falta tradução. Deixe en se inglês for sua base.

Pronto. A partir daqui o TranslationServer conhece suas tabelas.

tr(): traduzindo texto no código

Todo node do Godot tem o método tr(). Ele recebe a chave e devolve a string no idioma ativo:

extends Control

@onready var lives_label = $HUD/LivesLabel

func update_hud(lives: int) -> void:
    lives_label.text = "%s: %d" % [tr("HUD_LIVES"), lives]

Se a chave não existir na tabela, tr() devolve a própria chave. Isso é ótimo pra QA: qualquer DIALOG_GREETING cru aparecendo na tela denuncia uma tradução faltando.

Placeholders: nunca concatene frases

O erro clássico de localização é montar frase por concatenação: tr("HELLO") + ", " + player_name + "!". Isso quebra em qualquer idioma com ordem de palavras diferente. O jeito certo é deixar o placeholder dentro da string traduzida e preencher com format():

var greeting = tr("DIALOG_GREETING").format({"name": player_name})
# en:    "Hello, Maria!"
# pt_BR: "Olá, Maria!"

Assim o tradutor decide onde o nome entra na frase, que é decisão dele, não sua.

Sobre plurais ("1 vida" vs "3 vidas"): o Godot tem o método tr_n() pra isso, mas as regras de plural só funcionam de verdade com arquivos gettext. Com CSV, o caminho honesto é criar duas chaves (HUD_LIFE_ONE, HUD_LIFE_MANY) e escolher no código, ou reformular o texto pra não precisar de plural ("Vidas: 3" resolve).

UI traduzida sem escrever código

Aqui está a parte que mais economiza trabalho: nodes de Control traduzem sozinhos. Se o text de um Label ou Button for exatamente uma chave da tabela (MENU_NEW_GAME), a engine substitui pela tradução na renderização, sem nenhuma linha de script.

Isso é controlado pela propriedade auto_translate_mode do node (em Editor > Auto Translate Mode no Inspector). O padrão é herdar do pai, então normalmente você não toca nela. O caso de desligar existe: um input de texto onde o jogador digita o próprio nome não deve passar pelo tradutor, então ali você marca Disabled.

Na prática: UI estática (menus, botões, títulos) fica 100% por chave no editor, e o tr() no código fica só pra texto montado dinamicamente.

Próximo nível
Quer aprender isso na prática?

No CursoGame.Dev você sai dos tutoriais soltos e constrói jogos publicáveis, com trilha progressiva, quests práticas e feedback real.

Conhecer a plataforma
+500 alunos4.9/5Garantia 7 dias

Trocando o idioma em runtime

Agora a parte que o jogador vê: o seletor de idioma nas opções. Trocar o locale ativo é uma chamada:

TranslationServer.set_locale("pt_BR")

E o melhor: todos os Controls com auto translate ligado atualizam na hora, sem recarregar cena. É por isso que vale deixar o máximo possível da UI no esquema de chaves do editor.

Texto dinâmico precisa de aviso

O que set_locale() não atualiza sozinho é texto que você montou por código, porque a engine não sabe refazer a sua string. Pra isso existe a notificação NOTIFICATION_TRANSLATION_CHANGED, que todo node recebe quando o idioma muda:

extends Control

var current_lives := 3

func _notification(what: int) -> void:
    if what == NOTIFICATION_TRANSLATION_CHANGED:
        _refresh_texts()

func _refresh_texts() -> void:
    $LivesLabel.text = "%s: %d" % [tr("HUD_LIVES"), current_lives]

O padrão que eu sigo: toda cena com texto dinâmico tem um método _refresh_texts(), chamado no _ready() e na notificação. Um lugar só pra montar texto, zero estado desatualizado.

O seletor de idioma completo

Um OptionButton com os idiomas, salvando a escolha num ConfigFile pra persistir entre sessões:

extends OptionButton

const SETTINGS_PATH = "user://settings.cfg"

# Pares [rótulo no próprio idioma, código de locale].
# O rótulo fica no idioma nativo de propósito: quem não lê
# português precisa achar o idioma dele sem ajuda.
const LANGUAGES = [
    ["English", "en"],
    ["Português (Brasil)", "pt_BR"],
    ["Español", "es"],
]

func _ready() -> void:
    for i in LANGUAGES.size():
        add_item(LANGUAGES[i][0])
        set_item_metadata(i, LANGUAGES[i][1])
        if LANGUAGES[i][1] == TranslationServer.get_locale():
            select(i)
    item_selected.connect(_on_language_selected)

func _on_language_selected(index: int) -> void:
    var locale: String = get_item_metadata(index)
    TranslationServer.set_locale(locale)
    _save_locale(locale)

func _save_locale(locale: String) -> void:
    var config = ConfigFile.new()
    config.load(SETTINGS_PATH)  # carrega o que já existe, se existir
    config.set_value("settings", "locale", locale)
    config.save(SETTINGS_PATH)

E na inicialização do jogo (um autoload serve bem), você aplica o idioma salvo, ou detecta o do sistema na primeira execução:

extends Node

func _ready() -> void:
    var config = ConfigFile.new()
    if config.load("user://settings.cfg") == OK:
        TranslationServer.set_locale(config.get_value("settings", "locale", "en"))
    else:
        # Primeira execução: usa o idioma do sistema operacional.
        # OS.get_locale() devolve algo como "pt_BR".
        TranslationServer.set_locale(OS.get_locale())

Se o locale do sistema não tiver tradução, a engine cai no fallback configurado no projeto. Comportamento são, de graça.

Localização vai além do texto

Traduzir string é o grosso do trabalho, mas três pontos completam uma localização decente.

Recursos por idioma. Uma textura com texto desenhado, um áudio narrado, um vídeo: o Godot troca o arquivo inteiro por locale via Project Settings > Localization > Remaps. Você aponta o recurso base e adiciona uma variação por idioma. Quando a cena carrega a textura, a engine entrega a versão do locale ativo. Dito isso, a melhor prática é evitar texto dentro de arte sempre que possível: um Label em cima de uma placa sem texto custa nada pra traduzir, uma textura nova por idioma custa um artista.

Fonte com cobertura de glifos. Sua fonte estilosa pode não ter os caracteres de outros idiomas. Acentos do português costumam estar cobertos, mas cirílico, japonês ou coreano raramente estão. Teste cada idioma alvo de verdade na tela, e configure fallback de fonte no theme quando precisar.

Espaço na UI. Texto em alemão estoura caixa dimensionada pra inglês com frequência. Antes de contratar tradução, o Godot tem pseudolocalization (em Project Settings, com Advanced Settings ligado): ele alonga e decora todas as strings automaticamente pra você ver onde a UI quebra. Liga, navega pelos menus, anota o que estourou, corrige os containers. Meia hora disso economiza retrabalho real depois.

Fechando

O sistema de localização do Godot é simples e funciona: chaves no lugar de texto, CSV como fonte da verdade, tr() pra texto dinâmico, auto translate pra UI estática e set_locale() pra trocar tudo em runtime.

A única decisão que importa de verdade é quando começar. Adotar chaves desde o primeiro menu custa quase nada. Adaptar um projeto pronto custa semanas de caçada a strings. Se o seu jogo tem qualquer ambição de ir além do Brasil, e com o tamanho do mercado em inglês ele deveria ter, monte o text.csv hoje, mesmo que por enquanto ele só tenha uma coluna de idioma.