Introdução
Hoje em dia a maioria das aplicações que utilizamos (principalmente as baseadas na Internet) permitem a apresentação do seu user interface (UI) em várias línguas. Esta é uma das funcionalidades de internacionalização e localização de aplicações.
Também é comum as aplicações terem conteúdos dinâmicos (como opções de menu, links, listas de opções, etc.), que podem estar guardados numa Base de Dados (BD). Se a aplicação for globalizada, então é necessário termos os conteúdos provenientes da BD em várias línguas também.
Neste artigo vamos analisar um mecanismo que permite guardar conteúdos em múltiplas línguas, numa BD e fazer a sua leitura de uma forma consistente e automática, na língua pretendida.
Scripts: todos os scripts com os exemplos deste artigo podem ser encontrados aqui.
Conteúdo
Compreender o problema
Num mundo globalizado como o actual, é essencial desenvolver aplicações globalizadas, i.e. que possuam mecanismos automáticos de internacionalização e localização.
Neste contexto, entende-se globalização como a capacidade das aplicações apresentarem mensagens em diferentes línguas e culturas (moeda, formato números, unidades medida, etc.), dependendo da localização do utilizador.
As diversas tecnologias e frameworks de programação de software têm apresentado, ao longo do tempo, mecanismos de globalização que permitem, com relativa facilidade, apresentar mensagens localizadas, conforme o objectivo mencionado no parágrafo anterior. Tipicamente, os textos das mensagens são colocados em ficheiros de resources separados por cada Língua/Cultura (p.ex. pt-PT=Português de Portugal ou pt-BR= Português do Brasil), que são chamados automaticamente, conforme a cultura activa.
Estes mecanismos funcionam para textos que estejam embebidos no próprio código fonte das aplicações. Todavia, é comum as aplicações terem textos e conteúdos dinâmicos guardados em BD. Vejamos dois exemplos:
- Uma dada aplicação tem menus dinâmicos, cujas opções dependem da licença de software e do perfil de cada utilizador. As várias opções dos menus (etiqueta a apresentar, perfil de acesso, recursos, etc.) estão guardadas na BD.
- Numa dada aplicação é pedido ao utilizador que preencha os seus dados pessoais, existindo alguns campos em que tem que escolher um valor duma lista, como p.ex. estado civil, regime casamento, género, tipo de documento identificação, habilitações literárias, etc. Estas listas de valores estão guardadas em tabelas na BD.
No primeiro caso temos as etiquetas das opções dos menus que necessitam ser globalizadas, no segundo caso, temos dados de referência (as listas de valores) que necessitam ser globalizadas, também.
A questão que se coloca é como fazer a globalização destes dados, guardados em BD.
Visão geral
Com este mecanismo, a BD mantém a estrutura das tabelas originais, com os dados de negócio. Estas tabelas vão conter os dados no idioma e cultura neutro ou por defeito (para o presente artigo, vamos considerar que é pt-PT).
Vai ser criada uma tabela adicional, que regista as informações localizadas para os campos das outras tabelas, que necessitem tradução.
Como alternativa a ter uma única tabela com a informação localizada para TODAS as outras tabelas, esta pode ser dividida por várias tabelas, dependendo, por exemplo, da área funcional. Em qualquer dos casos, deve sempre ser muito claro qual a tabela de localização a utilizar.
As principais vantagens deste método são as seguintes:
- Tem pouco impacto sobre o desenvolvimento da aplicação, porque toda a selecção da localização pode ser realizada na BD. A única questão é que todas as consultas à BD devem ser realizadas por meio de uma Stored Procedure (SP), com o código de cultura como parâmetro;
- O método de localização permite utilizar valores predefinidos ou por defeito, que geralmente são os valores da tabela principal;
- Apenas os dados localizados são registados, nenhum dado desnecessário é replicado;
- É muito fácil mudar para uma abordagem não globalizada. Consultam-se simplesmente os dados das tabelas principais e não se utiliza a tabela localizada.
Objectos de Base de Dados utilizados pelo Mecanismo
Este método pode ser aplicado a qualquer tido de BD. Para efeitos da descrição e dos exemplos apresentados neste artigo, vamos utilizar o Microsoft SQL Server.
Objecto | Tipo | Descrição |
CultureInfo | Tabela | Esta tabela irá registar as informações de referência para os idiomas e culturas disponíveis no sistema. Todos os dados localizados devem ter uma chave externa com o identificador de cultura obtido desta tabela. |
ObjectGlobalization | Tabela | Esta tabela irá registar a informação localizada para todas as tabelas na base de dados, utilizando um modelo EAV (Modelo Entidade Atributo Valor). Cada linha tem uma chave que identifica a tabela, a coluna, a cultura e o Id da linha para os dados localizados. |
GetGlobalizedValue | Função | Esta é uma função auxiliar que permite ler o valor localizado a partir da tabela anterior, utilizando os campos-chave como parâmetros. |
GetGlobalizedTextValue | Função | Esta função é uma variação da função anterior que lê apenas valores de texto (nvarchar). |
usp_SaveGlobalizedValue | SP | Esta é uma SP auxiliar, para guardar os dados localizados na tabela ObjectGlobalization |
Tabela CultureInfo
CultureName | DisplayName | ISO_639x_Value | Culturecode |
af-ZA | Afrikaans - South Africa | 0x0436 | AFK |
ar-AE | Arabic - United Arab Emirates | 0x3801 | ARU |
ar-BH | Árabe - Bahrain | 0x3C01 | ARH |
ar-DZ | Árabe - Algeria | 0x1401 | ARG |
O conteúdo da tabela CultureInfo é trivial.
Tabela ObjectLocalization
ObjectName | ColumnName | Culture | RowId | Value |
dbo.PortType | Name | pt-PT | 1 | Porto Marítimo |
dbo.PortType | Name | pt-PT | 2 | Porto Seco |
GLOBAL_VALUE | Description | en-US | 0 | No |
GLOBAL_VALUE | Description | en-US | 1 | Yes |
GLOBAL_VALUE | Description | en-US | All | All |
GLOBAL_VALUE | Description | pt-PT | 0 | Não |
GLOBAL_VALUE | Description | pt-PT | 1 | Sim |
GLOBAL_VALUE | Description | pt-PT | All | Todos |
Coluna | Tipo | Descrição |
ObjectName | Varchar(128) | Normalmente esta coluna regista o Nome de tabela. Para casos especiais, pode utilizar um nome de convenção, como GLOBAL_VALUE |
ColumnName | Varchar(128) | Este é o nome da coluna que deseja localizar |
Culture | Varchar(10) | Esta é a chave externa para a tabela CultureInfo |
RowId | Sql_Variant | Regista o valor na coluna de Id da linha na tabela original. Este valor de Id pode ser qualquer tipo de dados do SQL. |
Value | Sql_Variant | Esta coluna regista o valor localizado |
ValueText | Varchar(max) | Esta coluna regista o valor de texto localizado. Ela pode ser utilizada como uma alternativa à coluna de valor |
ValueXML | XML | Esta coluna regista o valor localizado XML. Ela pode ser utilizada como uma alternativa à coluna de valor também |
Sempre que for necessário criar um valor localizado, basta inserir uma nova linha na tabela ObjectGlobalization com o nome da tabela, o nome da coluna e o valor de Id para identificar a linha e o valor localizado. Existe uma SP para executar esta tarefa.
Function GetGlobalizedValue
Parâmetro | Tipo | Descrição |
@ObjectName | Varchar(128) | O nome do objecto a pesquisar na tabela ObjectGlobalization |
@ColumnName | Varchar(128) | O nome da coluna a pesquisar na tabela ObjectGlobalization |
@Culture | Varchar(10) | O nome da cultura a pesquisar na tabela ObjectGlobalization |
@RowId | Sql_Variant | O Id da linha a pesquisar na tabela ObjectGlobalization |
@Default | Sql_Variant | Este é o valor por defeito, opcional, para a função, se a consulta sobre a tabela ObjectGlobalization não retornar dados. É tipicamente o valor da tabela original (cultura predefinida) |
Exemplos de utilização:
FROM PortType
Este exemplo lê o valor localizado, em Francês, da coluna Name da tabela PortType, utilizando o valor da coluna PortTypeName original como valor predefinido.
Este exemplo define a variável @label para o valor localizado em Português da string 'All' com o valor em Inglês predefinido.
Neste caso, o nome de objecto, nome de coluna e identificador de linha são convenções, uma vez que não é um valor de uma tabela.
Function GetGlobalizedValueText
Faz o mesmo que a função anterior, com a única diferença de que o valor retornado é obtido a partir da coluna ValueText na tabela ObjectGlobalization.
Stored Procudure usp_SaveGlobalizedValue
Faz o mesmo que a função anterior, com a única diferença de que o valor retornado é obtido a partir da coluna ValueText na tabela ObjectGlobalization.
Parâmetro | Tipo | Descrição |
@ObjectName | Varchar(128) | O nome do objecto a registar na tabela ObjectGlobalization |
@ColumnName | Varchar(128) | O nome da coluna a registar na tabela ObjectGlobalization |
@Culture | Varchar(10) | O nome da cultura a registar na tabela ObjectGlobalization |
@RowId | Sql_Variant | O Id da linha a registar na tabela ObjectGlobalization |
@Value | Sql_Variant | O valor localizado para coluna especificada a registar na tabela ObjectGlobalization |
@ValueText | Varchar(max) | Um valor de texto localizado alternativo, para a coluna especificada, a registar na tabela ObjectGlobalization. Em casos práticos, este será o valor usado principalmente. |
@ValueXml | XML | Um valor XML localizado alternativo, para a coluna especificada, a registar na tabela ObjectGlobalization |
@username | Varchar(10) | Um parâmetro opcional para registar o nome do utilizador responsável pelo registo da informação localizada. Se este parâmetro não for especificado, o utilizador do sistema será registado. |
Quando for necessário criar ou actualizar um registo na tabela ObjectGlobalization deve-se utilizar esta SP.
Exemplos de utilização:
@ObjectName = 'PortType',
@ColumnName = 'Name',
@Culture = 'fr-FR',
@RowID = 333,
@Value = NULL,
@ValueText = 'Port au Prince',
@ValueXml = NULL,
@username = 'kzinga'
Este exemplo regista (ou actualiza) a informação localizada para um dos valores em Francês que seriam lidos no exemplo anterior na função GetGlobalizedValue.
Considerações alternativas
Existem vários métodos de localização de BD, que podem ser tipificados nas secções que se seguem.
Embora cada um destes métodos apresente as suas vantagens, existem algumas desvantagens significativas em comparação com o método de Globalização de Objectos apresentado anteriormente.
Base de Dados Clone
Utilizando este método cria-se um clone completo da BD. O resultado é uma cópia exacta da estrutura original, incluindo todas as tabelas e campos. As BDs diferem apenas no nome e os conteúdos em diferentes idiomas.
Este método tem as desvantagens de exigir uma gestão complexa de conexões e uma duplicação desnecessária de dados (de todos os campos não localizados).
Field Database Localization
Utilizando este método, os valores dos campos localizados são criados na mesma linha da tabela. Os campos localizados são iguais ao campo original, com a excepção de conterem dados num idioma diferente e, é claro, os seus nomes diferem.
A principal desvantagem consiste no facto de que o código deve escolher dinamicamente a coluna com base no idioma selecionado e, claro, parece uma boa solução quando se começa com 2 idiomas, mas se o sistema cresce e precisar de um 3 º idioma? E um 4º? …
Linha Clone
Cada linha da tabela original é copiada para cada idioma. As linhas copiadas são iguais ao original, com a excepção de o valor do campo de idioma ser diferente. O resultado pode ter esta visualização:
As principais desvantagens são que, embora tenha pequeno impacto sobre o desenvolvimento da aplicação (o idioma é usado como chave para selecionar a linha e não na coluna) o conteúdo da tabela vai crescer e vai aumentar a complexidade de administrá-la.
Tabela Clone
Este método adiciona tabelas localizadas para as tabelas que se deseja localizar. As tabelas localizadas contêm somente a chave primária e os campos que são localizados. O resultado é escrito nas novas tabelas com a mesma estrutura. As tabelas de base de dados só têm nomes diferentes, como Descricao e Descricao_ja:
Outra opção para este método é ter a tabela original com todos os campos e a tabela localizada apenas com códigos de idioma e os campos localizados. É uma espécie do exemplo de Linha Clone, mas com as linhas localizadas numa tabela separada.
Tal como o método de localização de campo, a principal desvantagem é que o código deve escolher dinamicamente o nome de tabela com base no idioma selecionado. E se o número de idiomas utilizados pelo sistema começar a crescer, acabará com um número excessivo de tabelas.
Referências
http://www.sisulizer.com/localization/software/server-desktop-database.shtml
http://www.sqlservercentral.com/Forums/Topic205432-230-1.aspx
Sem comentários:
Enviar um comentário