Introdução
Uma situação real para mim aqui no Japão desenvolvendo aplicativos é não ter domínio do idioma japonês. Dessa forma ao, por exemplo, enviar mensagens ao usuário em japonês, quando eu mesmo estou realizando testes, debug, muitas vezes não sei do que se trata a mensagem. É isso é vida real de desenvolvedor. Poderia utilizar ferramentas para tradução e traduzir o aplicativo inteiro, mas não preciso disso. Ao contrário do que muitos podem pensar, aqui no Japão só começa agora aparecerem aplicativos multiidioma. Já encontramos em hospitais e algumas lojas. Mas no meu caso desenvolvendo aplicativos administrativos para empresas japonesas essas empresas só querem em japonês mesmo.
Então resolvi criar um mecanismo que pudesse resolver o meu problema específico, ser leve eficiente e que não demandasse muito tempo para criá-lo. Resolvi utilizar o mesmo que muitos aplicativos web utilizam, ter um arquivo de idiomas e consumi-los.
Título (SEO)
Tradução FMX sem drama: Singleton de instância + “EN|JA|PT” em resourcestring (e por que não quebrei meu código)
Subtítulo
Uma abordagem pragmática para i18n em Delphi/FMX: helpers mínimos, portável para Windows/Android/iOS/macOS, e zero “mágica”.
TL;DR
- Usei
resourcestringcontendo"EN|JA|PT"e um TranslationManager que seleciona o trecho certo conforme idioma. - Tornei o manager um singleton de instância sem
class var(armazeno a instância em variáveis de módulo), para evitar o infame E2356. - Ergonomia com helper
_()(ou String Helper.T/.TF). - Portável (FMX): funciona em Windows + mobile; Resource DLLs ficam como alternativa somente Windows.
Motivação
- Queria i18n rápido, portável e sem vendor lock.
resourcestring+ Resource DLL é oficial, mas Windows-only.TLangé legal, mas precisei de menor atrito e placeholders simples (%s).- Solução:
"EN|JA|PT"emresourcestring+ TranslationManager.
O design (em 60 segundos)
- Cada
resourcestringguarda as 3 versões:EN|JA|PT. TranslationManager.GetLocalizedStringfazSplit('|'), escolhe pelo idioma, remove pontuação final (opcional) e aplicaFormat.
Uso:
GlobalTranslationManager.SetLanguage(langPortuguese);
ShowMessage(_(rsMSG_APT_FOUND));
Helpers globais para ergonomia:
function _(const rs: string): string;
begin
Result := TTranslationManager.Instance.GetLocalizedString(rs);
end;
Singleton de instância com variáveis de módulo:
// implementation
var
GInstance: TTranslationManager;
GLock: TObject;
class function TTranslationManager.Instance: TTranslationManager;
begin
if GInstance = nil then
begin
TMonitor.Enter(GLock);
try
if GInstance = nil then
GInstance := TTranslationManager.Create(langJapanese);
finally
TMonitor.Exit(GLock);
end;
end;
Result := GInstance;
end;
Por que variáveis de módulo? Porque misturar class var com accessors de properties de instânciacostuma levar ao E2356. Mantendo só membros de instância dentro da classe, o compilador não confunde nada.Ponto chave: evitar o E2356
O erro “Property accessor must be an instance field or method” aparece quando você:
- Declara
class var FCurrentLanguagee tenta usá-lo comoread FCurrentLanguagenuma property de instância, ou - Usa
class procedure SetLanguagecomowrite SetLanguagenuma property de instância.
Fix: mantenha FCurrentLanguage e SetCurrentLanguage de instância; leve o estado do singleton (GInstance, GLock) para o escopo da unit (implementation). Pronto.
Ergonomia: _() vs String Helper
String Helper (opcional):
rsMSG_APT_FOUND.T
rsMSG_APT_FLOOR_INFO.TF(['101','3','A'])
_(): curtos e claros.
ShowMessage(_(rsMSG_APT_FLOOR_INFO, ['101','3','A']));
Traduzindo a UI inteira
Use TagString dos controles com “EN|JA|PT” e percorra a árvore:
procedure TranslateControls(const Root: TFmxObject);
begin
// se houver 'Text' e TagString, aplica _(TagString) em Text
end;
Chame ao trocar o idioma (e/ou amarre no OnLanguageChanged do manager).
Portabilidade e alternativas
- Esta solução: funciona em Windows, Android, iOS, macOS (FMX).
- Resource DLLs (oficial Delphi): ótimo para Windows puro, elimina
_(), mas não cobre mobile; carrega viaLoadNewResourceModule. - TLang: boa integração FMX, arquivos de tradução, mas muda sua rotina de strings; escolha se quiser alinhar ao ecossistema FMX.
Armadilhas & Dicas
- Placeholders: mantenha a mesma ordem nas 3 línguas (
%s/%d). - Pipes literais: se precisar de
|no texto, defina um escape (\|) e trate antes doSplit. - Pontuação final: eu removo (
.。.。). Ajuste conforme sua UI. - Ordem de finalização: ok deixar o SO limpar (apps), mas liberar em
finalizationtambém funciona — evite acessar o manager depois disso. - Cache: para UIs grandes, cacheie
rs → traduçãopor idioma.
Quando não usar este padrão
- Se você precisa de tradução por arquivos/DLL gerenciados por tradutores externos e quer dispensar qualquer
Split. - Se sua equipe quer padronizar em TLang e arquivos
*.strings.
Checklist (para copiar e colar no projeto)
-
resourcestringcom"EN|JA|PT" -
TranslationManager(singleton de instância viaGInstance/GLock) - Helpers
_()ou String Helper.T/.TF -
TranslateControls(Self)quando trocar idioma - Tests: placeholders, fallback, pontuação,
\|
Conclusão
i18n não precisa virar um projeto. Com resourcestring + TranslationManager você obtém uma solução simples, portável e sob controle — e ainda pode migrar para Resource DLLs depois se (e só se) fizer sentido.
Tags
delphi · fmx · i18n · localization · design-patterns · singleton · thecodenaked
Quer que eu já gere o post completo com trechos de código prontos (incluindo o TranslateControls), num arquivo .md?