Internacionalização de ficheiros de JavaScript

August 29th, 2010

Os últimos posts no meu blog têm sido acerca do tema da internacionalização de aplicações, nomeadamente:

O post de hoje contínua o sobre o método alternativo com ASP.NET, recorrendo a ao GetText do GNU. Como vimos, a aplicação do método _() para substituir texto localizável tornava-se um recurso simples e eficiente na internacionalização de ASP.NET. Em especial, é um método adoptado pela restantes linguagens e evita o esforço extra necessário para introduzir o texto em ficheiros .resx.

Procurei uma solução semelhante para Javascript, uma vez que a aplicação que estou a internacionalizar recorre muito a funcionalidade em javascript. Em especial, há mensagens de erro específicos e mensagens de confirmação contextualizadas. Especifico a ASP.Net, não encontrei nada de útil. Até achei muito estranho não detectar nada de muito útil nesse campo.

De qualquer forma, há alguns métodos genéricos a aplicar, incluíndo plugins em jQuery (http://plugins.jquery.com/project/gettext), mas gostei da descrição dada em 24ways.org. Simplesmente introduzi a função para _() no meu ficheiro core de javascript (é uma pequena biblioteca e funções e abstracções comuns a todas as páginas das minhas aplicações, e incluído em todas as páginas):

function _(s) {
	if (typeof(i18n)!='undefined' && i18n[s]) {
		return i18n[s];
	}
	return s;
}

Com isto é esperado existir uma variável i18n definido com a lista de traduções em pares chave-valor em JSON, do tipo:

var i18n = {
"" : "",
"Hello" : "Olá",
"Goodbye" : "Adeus",
//(...)
}

O método em 24ways.oprg refere ainda funções para formatar strings que podem ser uteis. Eu no entanto já tinha uma implementação de string.format para javascript (http://www.geekdaily.net/2007/06/21/cs-stringformat-for-javascript/).

Portanto, com a função no core, e a variável i18n adicionada, nos diversos ficheiros de javascript, onde fosse usado uma string, este deveria ser englobado por _():

if (confirm(_('Confirma a alteração de estado nos processos seleccionados?') )) {
//...
}

Assim, quando executado, e porque o método _() está sempre presente, o texto será substituído pelo existente no dicionário ou se não encontrado, o próprio texto usado como argumento da função.

Resta apenas dois passos – gerar os ficheiros .po, que deve seguir um esquema semelhante ao mencionado no artigo de ASP.Net, usando instruções póst-build e o gettext, e depois transformar os registos dos ficheiros .po em json. O primeiro dos passos é relativamente directa, apenas necessitando de construir uma nova lista de ficheiros (apenas de .js) e adicionar as entradas envolvidas em _() no .pot.

O segundo passo é mais “dificil”. Não há nada para o fazer directamentem senão um script em perl (http://jsgettext.berlios.de/doc/html/po2json.html), e como não me servia, decidi construir um script T4 simples, para usar no processo.

O script assume alguns pressupostos, pelo que, se utilizar, deve de os ter em conta e alterar consoante o necessário. O primeiro é que os ficheiros resultantes, quer os .po, quer os .js vão estar cada um numa pasta dedicada à língua específica como por exemplo:

/Scripts/locale/xx/messages.po
/Scripts/locale/xx/i18n.js

Também, considero que o ficheiro .po será gerado e unido com recurso a msgmerge, e portanto apenas devo transformar os ficheiros .po em ficheiros .js

Assim, o po2json.tt, colocado na pasta (/Scripts/locale/) pode ser executado após o build. Ele procurará o ficheiro .po de cada língua no array, e transformarará em um ficheiro i18n.js. Eu decidi definir as linguas manualmente, mas com pequenas alterações, podem ser detectadas automáticamente.

O T4 é o seguinte:

<#@ Template Language="C#v3.5" Hostspecific="True" #>
<#@ Output Extension=".js" #>
<#@ Import Namespace="System.IO" #>
<#@ Import Namespace="System.Collections.Generic" #>
<#@ Import Namespace="System.Text.RegularExpressions" #>
<#@ Include File="MultiOutput.t4" #>
var i18n = {
<#
    string localeFolderPath = @"D:\Codigo\ProjectoWeb\Scripts\locale\";
    string[] suportedLang = {"pt"};
    string pofileName = "messages.po";
    string msgid = "msgid";
    string msgstr = "msgstr";
    string txtREGEX = "\"(.*)\"";
 
    foreach (string lang in suportedLang)
    {
        string[] lines = File.ReadAllLines(localeFolderPath + "\\" + lang + "\\"  + pofileName);
        List<KeyValuePair<string, string>> entries = new List<KeyValuePair<string, string>>();
        int currentLine = 0;
 
        while (currentLine < lines.Length)
        {
            string line = lines[currentLine];
            if (line.StartsWith(msgid))
            {
                string keyString = "";
                keyString += Regex.Match(line, txtREGEX).Groups[1].Value;
 
                //iterate while still a msgid
                line = lines[++currentLine];
                while(line.StartsWith("\""))
                {
                    keyString += Regex.Match(line, txtREGEX).Groups[1].Value;
                    line = lines[++currentLine];
                }
 
                if (line.StartsWith(msgstr))
                {
                    string valueString = "";
                    valueString += Regex.Match(line, txtREGEX).Groups[1].Value;
 
                    if (++currentLine < lines.Length)
                    {
                        line = lines[currentLine];
                        while (line.StartsWith("\""))
                        {
                            valueString += Regex.Match(line, txtREGEX).Groups[1].Value;
                            line = lines[++currentLine];
                        }
                    }
                    //add pair to entries list
                    entries.Add(new KeyValuePair<string, string>(keyString, valueString));
                }     
            }
 
            currentLine++;
        }
 
        //build outoput
        for( int i = 0; i< entries.Count; i++)
        {
            KeyValuePair<string, string> entry = entries[i];
 
#>
"<#=  entry.Key #>" : "<#= entry.Value #>"<#=    i<entries.Count-1 ? "," : "" #>
<#
        }  
    SaveOutput(localeFolderPath + "\\" + lang + "\\"  + "i18n.js"); 
 
    }
#>
};

Procura cada “msgid” e processa as línguas seguintes convenientemente. Se houver erros, o processo será abandonado. O tt recorre ainda a um MultiOutput.t4, para gerar mais que um ficheiro a partir do .tt, convenientemente localizado. Não necessita de estar incluído no projecto (para não obrigar a incluir as dependências de TextTempleting).

Para incluir o ficheiro na lista de scripts a puxar, basta regista-lo no ScriptManager, no Page_Load da MasterPage :

ScriptManager.RegisterClientScriptInclude(this, typeof(string), "i18n", Page.ResolveUrl("~/Scripts/locale/" + lang + "/i18n.js"));

onde a variável lang é uma string com as letras da língua, para identificar a pasta onde será armazenado. Eu obtenho a a língua para a aplicação (transversal), mas outra possibilidade seria definir a língua pelas preferências do utilizador. Também, o caminho poderia ser proveniente de uma chave de configuração no web.config, se necessário.

Creio que está é uma solução simples, que pode, se necessário ser melhorado com mais funções (especialmente de formatações e plural / singular) como descrito no artigo do 24ways.org. Para já, esta é me suficiente.

Download po2json t4

ASP.Net – Método alternativo de internacionalização (i18n)

August 25th, 2010

A internacionalização de aplicações (internationalization – i18n) para posterior localização (localization – l10n) é um processo multi-passo, que envolve a detecção de texto que deve ser apresentado em múltiplas línguas, bem como a preparação da base de código para que o texto possa ser correctamente traduzido.

Cada tecnologia tem a sua própria forma de implementar mecanismos que auxiliam a tradução. .Net recorre a recursos compilados na forma de ficheiros .resx . Os ficheiros .resx são de uma forma geral pares chave-valor, estruturados em XML, que são compilados para dlls carregados no inicio da aplicação.

Apesar de uma solução interessante, é extremamente tediante usa-los em ASP.Net. Com as bibliotecas de uma API, ok. O suporte a nível de código C# (ou VB ) torna o uso relativamente simples. Já com ASP.Net, não é possível dizer o mesmo. As razões estão bem descritas em “Why Internationalization is hopelessly Broken in ASP.Net“, mas resumindo, tudo tem de ser controlos ASP.Net com o runat=”server”, não há suporte simplificado a texto estático (escrito directamente na página), a referência de chaves das strings obriga a inserir meta:resoursekey=”" em todas os controlos, e os ficheiros .resx não são facilmente legíveis por pessoal não técnico.

A alternativa? Seguir o método do “resto do mundo” e usar ficheiros de strings em formatos simplificados, nomeadamente os ficheiros .po. Quem já explorou um projecto open-source ou projectos aplicacionais com add-ins linguísticos, muito provavelmente já se cruzaram com este formato de ficheiros. São ficheiros de texto simples, com texto assoiado a uma chave (msgid) e texto associado à tradução (msgstr). Por exemplo:

msgid "Pesquisar Candidatos"
msgstr "Search Candidates"

De notar neste exemplo que a chave está em PT e a tradução em EN. Não é recomendado esta abordagem, uma vez que as línguas latinas tem acentos que não são óptimos para chaves.

Continuando, o uso de ficheiros .PO, noutras frameworks, é suportado por por uma função gettext(), onde é passo a chave (geralmente o próprio texto, na língua base) e retorna a versão traduzida, conforme as preferências de utilizador. Melhor, há geralmente uma forma simplificada da função escrito _(). Assim, cada string a analisar chama a função gettext(), obtendo a versão traduzida do texto. Bem mais simples que os .resx e meta:resourcekey do .NET.

Usando Fairlylocal

O FailyLocal é um projecto opensource iniciado por Jason Kester, e que implementa a funcionalidade do gettext e ficheiros PO para ASP.Net. Como descrito no link do projecto, para pedir a tradução, na página basta utilizar:

<%= _("texto a traduzir") %>

ou no codebehind:

controlo.InnerHtml = _("texto a traduzir");

Tão simples e eficaz. Em termos de biblioteca, basta adicionar os ficheiros ao projecto da aplicação (têm de ser um projecto de aplicação web, em vez de um projecto de website, uma vez que o segundo não permite evento post-build, que o primeiro permite, e que constrói dinamicamente os ficheiros PO).

Em termos de execução, os ficheiros PO são carregados no arranque da aplicação, para um dicionário estático em memória, onde depois são realizadas as consultas do gettext(). Neste caso, todas as paginas devem herdar de InternationalPage ou InternationalMaster (ou a página base da aplicação derivar destas) em vez de Page directamente, para que _() fique disponível.

Para as minhas necessidades, efectuei pequenas alterações. Primeiro, para facilitar o uso em mutlipos projectos, adicionai os ficheiros ao projecto da minha framework, para que tenha sempre disponível. Segundo, porque necessito de aceder aos métodos, quer para a página instanciada, quer quando estou a usar WebMethods, tornei a propriedade LanguageCode e o método _() static. Finalmente, porque necessito de definir a língua ao nível da aplicação e não para cada utilizador, modifiquei o getter de LanguageCode para obter o código de lingua directamente da thread, em vez da sessão do utilizador:

public static string LanguageCode
{
   get
   {
       if (_languageCode == null)
       {
           _languageCode = System.Threading.Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName.ToLower();
	}
	return _languageCode;
    }
    set
    {
	_languageCode = value;
    }
}

Incluíndo os scripts post-build, os ficheiros .PO são actualizados após cada build, podendo ser traduzidos de seguida, ou directamente ou recorrendo a programas como o Poedit. Ferramentas como o Pepipopum permitem traduzir automaticamente (via Google translate API) o ficheiro PO. As chaves devem estar em inglês para funcionar correctamente.

Finalmente, convém referir que o sistema tem fallback automático para o texto da própria chave – se o testo não estiver traduzido no ficheiro .po, o texto a usar é o próprio argumento do _(), o que ajuda muito se tiver a internacionalizar uma aplicação já existente.

Uma macro util

Ainda assim, com esta simplificação, internacionalizar uma aplicação existente não deixa de ser um trabalho tediante. Localizar o texto a traduzir e envolver no método _() é “chato”.

Para ajudar, criei uma macro:

Public Module GetTextSurrond
 
    '' surround text with <%= _(" and ") $>
    ''
    Sub ASPXSurround()
        Dim textSelection As EnvDTE.TextSelection
 
        textSelection = DTE.ActiveDocument.Selection()
        textSelection.Text = "<%= _(""" + textSelection.Text + """) " ''%>"
    End Sub
 
End Module

Associando esta Macro a um atalho de teclas, por exemplo Ctrl+G, Ctrl+T, ajudará a usar a macro, bastando seleccionar o texto e executar o comando (nos ficheiros .aspx).

Conclusão

Pela curta experiência que tenho com esta alternativa de tradução, não deixa de me parecer N vezes mais vantajoso que o uso de ficheiros .resx no ASP.Net. Os ficheiros .PO são apenas strings, portanto para recursos binários outra estratégia deve ser adoptada.

Localizar Enumerações

July 5th, 2010

Continuando no desafio de localizar a minha aplicação, estou agora a atacar as enumerações. Esta também é uma óptima oportunidade de efectuar algum refactoring às classes e estruturas existentes.

Parte das enumerações usadas são automaticamente escritas com uma estrutura que era óptima.. até agora. A aplicação é N-layer, dividido em camadas lógicas. Por enquanto, e porque ainda não foi necessário mais, mantém-se monotier. As camadas lógicas neste caso são UI, BLL (Business Lógic Layer) e DAL (Data Access Layer), mais os repositórios de dados. Comum a todas as camadas há duas bibliotecas – MAAPPFramework que é um conjuntos de classes base, interfaces base, e muitos helpers comuns a várias aplicações (O MAAPP significa Miguel Alho Application : D ) e a camada de BO (Business Objects) que inclui as classes conceptuais e DTOs (Data Transfer Objects) do domínio. Os objectos nesta biblioteca são “dummy objects”, no sentido que não tem funcionalidade (senão algumas ordenações e extensões). Apenas suportam dados e estão devidamente decoradas para serem serializadas. Esta biblioteca é transformada em dll e utilizada por todas as camadas. Esta arquitectura está muito bem exemplificado pelo Imar Spaanjars nos seus artigos Building Layered Web Applications with Microsoft ASP.NET 2.0.

As enumerações que eu tinha incluíam sempre um valor numérico definido explicitamente (evitando súbitas alterações de referências para com a base de dados numa nova geração de código) e uma descrição com texto legível, em vez de um texto colado em CamelCase. um exemplo:

namespace Namespace.BO
{
    [Serializable]  
    public enum EnumTipoContacto
    {	
        [Description("Telefone")]
	Telefone = 1,
        [Description("Telemóvel")]
        Telemovel = 2,
        [Description("Email")]
        Email = 3,
        [Description("Fax")]
        Fax = 4,
        [Description("Outro Tipo de Contacto")]
        Outro = 5
    }
}

A descrição escrita como atributo, recorrendo ao System.ComponentModel.DescriptionAttribute permite em runtime ter uma versão legível do nome da enumeração, óptima para apresentação na UI, em vez do tipo valor devolvido pelo método ToString(). No exemplo em cima, O caso do “Outro” demonstra a possível diferença. Para obter os valores, na minha framework tenho um método numa helper class que permite obter o valor da descrição, bem como uma extension method para ajudar:

public class EnumDescription
{
        public static string GetDescription(Enum value)
        {
            FieldInfo fieldInfo = value.GetType().GetField(value.ToString());
            DescriptionAttribute[] attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
            return (attributes.Length > 0) ? attributes[0].Description : value.ToString();
        }
 
        //(...)
}
 
public static class EnumExtension
{
    public static string GetDescription(this Enum e)
    {
         return EnumDescription.GetDescription(e);
    }
}

Considerando que o código é gerado a partir de um modelo que controlo, é uma óptima solução, desde que localizado a uma definição linguística. Seria possível criar múltiplos atributos (costumizados) para suportar descrições localizadas, por exemplo, mas a manutenção e separação não me parece optimizado. Procurei outra solução que me permitiria utilizar ficheiros de recursos (.resx).

Uma das técnicas que encontrei parece simples e óptima. A descrição passa a ser definida no ficheiro de recursos, e a chave é o nome qualificado da enumeração. Optei pelo nome completo porque é me fácil de gerar com T4. Assim, a minha enumeração fica simplificada:

[Serializable]
public enum EnumTipoContacto
{	
	Telefone = 1,
        Telemovel = 2,
        Email = 3,
        Fax = 4,
        Outro = 5,
}

e o meu ficheiro .resx terá um conjunto de chaves valores qualificados, que pode ser localizado na versão do ficheiro para outra língua:

(...)
<data name="Namespace.BO.EnumTipoContacto.Telefone" xml:space="preserve">
    <value>Telefone</value>
</data>  
 <data name="Namespace.BO.EnumTipoContacto.Telemovel" xml:space="preserve">
    <value>Telemóvel</value>
</data>  
 <data name="Namespace.BO.EnumTipoContacto.Email" xml:space="preserve">
    <value>Email</value>
</data>  
 <data name="Namespace.BO.EnumTipoContacto.Fax" xml:space="preserve">
    <value>Fax</value>
</data>  
 <data name="Namespace.BO.EnumTipoContacto.Outro" xml:space="preserve">
    <value>Outro Tipo / Desconhecido</value>
</data> 
(...)

Para obter o valor da chave, a operação é ligeiramente diferente:

public static string GetLocalizedDescription(EnumTipoContacto tipo)
{
      ResourceManager resources = Namespace.BO.Properties.Enum.ResourceManager;
 
      string resourceKey = String.Format("{0}.{1}", tipo.GetType(), tipo);
      string localizedDescription = resources.GetString(resourceKey);
 
      if (localizedDescription.IsNullOrEmpty())
           return tipo.ToString();
      else
           return localizedDescription;
}

Neste exemplo, tipo.GetType() devolve Namespace.BO.EnumTipoContacto, permitindo construir a chave. é necessário ter em atenção que linha

ResourceManager resources = Namespace.BO.Properties.Enum.ResourceManager;

obriga-me a que a assemblagem gerada seja o Namespace.BO.dll e o ficheiro de recursos é o Enum.resx ou Enum.xx.resx onde xx representa o código cultural (“es”, por exemplo). Novamente, porque o meu código é em boa parte autogerado, tenho controlo sobre esse formato.

Outro método muito útil, para usar como DataSource para preencher DropDownLists e controlos do género é a seguinte:

public static List<KeyValuePair<string, string>> GetDDLList()
{
            List<KeyValuePair<string, string>> kvPairList = new List<KeyValuePair<string, string>>();
            ResourceManager resources = Namespace.BO.Properties.Enum.ResourceManager;
 
            foreach (Enum enumValue in Enum.GetValues(typeof(EnumTipoContacto)))
            {
                string resourceKey = String.Format("{0}.{1}", enumValue.GetType(), enumValue);
                string localizedDescription = resources.GetString(resourceKey);
 
                if(localizedDescription.IsNullOrEmpty())
                    kvPairList.Add(new KeyValuePair<string, string>(enumValue.ToString(), enumValue.ToString()));
                else
                    kvPairList.Add(new KeyValuePair<string, string>(enumValue.ToString(), localizedDescription));
            }
 
            return kvPairList;
}

Semelhante à anterior, permite obter uma lista de KeyValuePairs aceites como DataSource dos controlos.

Este foi, numa fase inicial, o tipo de estrutura que mais me preocupava, no sentido que os atributos poderiam complicar a globalização, e obrigar a um refactoring interno profundo. Felizmente, um simples workaround e refactoring evitou grandes mexidas. Também, porque nem os nomes dos métodos (nomeadamente o GetDDLList() ) nem os tipos de dados foram alterados, a camada superior continua a funcionar sem ter sido afectado.

Adicionar novas línguas ao Windows (edições Home)

July 2nd, 2010

Eu desenvolvo sobre Windows, sobre um Windows 7 Home Premium, para ser mais exacto. E ao longo do tempo tenho sofrido de alguns obstáculos oferecido pelas limitações da versão. A versão é a que veio com o PC, e pretendo puxá-lo até onde possível, antes de fazer um upgrade para um Business ou Ultimate (apesar das restantes máquinas – desktop – aqui do escritório estarem com o Ultimate).

Mas infelizmente ontem, deparei-me com um problema que demorou a tarde toda a perceber, e quando assim é, é geralmente algo do OS e nada óbvio. Contextualizando, estou numa fase de internacionalizar uma aplicação web de gestão de currículos e processos de recrutamento e selecção, que desenvolvi no ano passado (e ainda este ano). Foi grande sucesso junto da equipa de trabalho, pois permitiu acelerar em muito os processos de recrutamento, que dado o volume de candidaturas que lhes chegavam – dezenas de milhares em meio ano – era tediante e constrangedor. Felizmente, a app foi criada à medida da equipa e adaptado ao processo de trabalho, e resultou em pleno.

De qualquer forma, a app está a ser internacionalizado para suportar as acções em Espanha, e o processo de internacionalização tem os seus próprios desafios associados – a globalização da aplicação para não ter dependências directas da língua nem numeração, e a localização para corresponder a cada realidade, quer de linguagem, quer de informação (por exemplo as diferenças de moradas e códigos postais e informação regional).

Sei que esta fase trará alguns problemas de estrutura e configuração – umas mais complicadas que outras – e a primeira apareceu logo na base de dados. Para a aplicação, optei pelo suporte de dados em base de dados relacional, e a base escolhida foi o PostgreSQL. Tem óptimas capacidades de armazenamento, suporte a pesquisa em XML, indexação e pesquisa de texto livre com o tSearch2, e é OpenSource que também foi muito atractivo na escolha.

Quando se cria uma base de dados no Postgre, o único parâmetro obrigatório é o nome da base de dados. As restantes definições são preenchidas com os valores pré-definidos da instalação. As que condicionam a base e que só podem ser definidas na criação são o LC_COLLATE e o LC_CTYPE. A primeira – LC_COLLATE, determina a forma como são ordenadas as palavras e que pode variar de cultura para cultura. O mesmo para o LC_CTYPE que caracteriza os caracteres em termos de maiúsculas, minúsculas, e pontuação

O problema destes parâmetros são a dependência do OS para esta informação. Seria muito interessante se dependesse apenas da base de dados e os executáveis associados, mas na verdade esta informação vem do sistema operativo. Por defeito, o valor para as variáveis são o da definições do OS. O Windows Server 2008 permite que o utilizador possa configurar a sua conta para definições regionais e linguísticas diferentes e que podem ser acrescentados através de MUIs (Multilingual User Interface). O mesmo é possível nas versões do Business e Ultimate do Windows. Infelizmente, no Home Premium não é possível, sem alguns workarounds.

Como afecta a criação da base? Simples. Se não tiver as definições linguísticas instaladas na máquina, não posso criar a base de dados usando essas definições, apenas com as existentes. Para portuguese, e portanto com base na definição do OS, tenho quer para LC_CTYPE, quer para LC_COLLATE o valor:

Portuguese_Portugal.1252

Para a base espanhola queria

spanish_Spain.1252

Acrescentar definição no Windows Server 2008

Trabalhamos aqui geralmente com duas instâncias de base de dados, uma local e uma remota, centralizada. Procurei resolver primeiro o caso no server, para verificar se era mesmo este o problema. Não tinha o pack espanhol instalado, e portanto puxei-o do site da Microsoft:

http://www.microsoft.com/Downloads/details.aspx?familyid=03831393-EEF7-48A5-A69F-0CE72B883DF2&displaylang=en

ou para o Server 2008 c/ SP2

http://www.microsoft.com/Downloads/details.aspx?familyid=3A7FB7A2-3519-495B-9BC5-2007082CA9A6&displaylang=en

ou para Server 2008 R2

http://www.microsoft.com/Downloads/details.aspx?familyid=03831393-EEF7-48A5-A69F-0CE72B883DF2&displaylang=en

são ficheiros .img pelo que devem ser carregados numa drive virtual como o Virtual Clone Drive.

Depois em Control Panel > Regional and Language Options > Keyboards and Languages > Display Language aparece a lista de linguagens disponíveis e em uso pelo Windows. Há tb um botão “Install/uninstall Languages” que permite acrescentar. Deve no Wizard que aparece escolher a pasta da drive para a linguagem especifica e deixar correr a instalação.

No caso do espanhol, puxei o ficheiro do grupo 1, e no wizard escolhi a pasta “es-ES”. “Et voilá”, spanish_Spain.1252 fica disponível no PostgreSQL. Simples e resolvido.

O processo para as versões do 7 Ultimate e Business devem ser semelhantes. O problema surge para o Home. Não é suportado, mas é possível introduzir as definições de localização. Para tal guiei-me pelas notas em http://xaueious.wordpress.com/2009/08/22/changing-installed-language-of-windows-7-home-premium-pro-from-en-us/ e segui o caminho da linha de comandos.

Acrescentar definição no Windows 7 Home Premium

  1. Descarregue o MUI associado à língua específica daqui http://www.froggie.sk/7lp64rtm.html
  2. Agora um dos truques: Se executares o ficheiro descarregado, é extraído um ficheiro “lp.cab” para a mesma pasta e que é fundamental preservar. No entanto, pouco depois de executar, o ficheiro desaparece. Portanto considera-o um pouco como um jogo de reacção, e prepara o ambiente para mover o ficheiro para outro local ou renomea-lo ASAP. Copy-paste não resulta.
  3. Na linha de comandos, executado com privilégios de administrador, executa o seguinte comando:
    DISM /Online /Add-Package /PackagePath:D:\Caminho\lp.cab

    onde D:\Caminho\lp.cab é o caminho completo para o ficheiro extraído.

  4. O processo demora uns minutos, mas uma vez findado, no Postgre, é possível criar a base com as chaves de locale correctas e desejadas.

Mover Ficheiros De Um Repositório Para Outro com Subversion.

June 30th, 2010

Ontem, num curta sessão de manutenção dos meus repositórios de Subversion, senti necessidade de reorganizar algumas, como creio ser “normal” ao fim de algum tempo. Tendo a organizar o meu SVN como múltiplos repositórios, um por projecto. Dentro de cada repositório tenho os diversos projectos de classes, sites ou aplicações associados. Tenho assim vários repositórios contextualizados e mais pequenos, em vez de um repositório geral com diversos projectos. A única dificuldade que apresenta é o backup / dump que obriga a manter um script com a lista de comandos de “svnadmin dump” para cada repositorio individual.

Também, apenas agora começo a usar a estrutura de pastas típicas do SVN – /trunk, /brenches e /tags, porque só agora tive necessidade de usar tags para marcar algumas situações. Mover as pastas dentre do repositório para incluir esta alteração é bastante simples, e com o ToirtoiseSVN no server, é drag n’ drop.

Durante a reorganização, senti necessidade de mover alguns repositórios – essencialmente de projectos de testes – e uni-los num só repositório contextualizado. Mover pastas dentro de um repositório é muito simples, usando o commando svn move ou usando o RepoBrowser do ToirtoiseSVN Repo Browser. Mas entre repositórios é um pouco mais elaborado. é preciso efectuar o dump do repositórios, e recarrega-lo no novo repositório, preferencialmente indicando a nova pasta para a qual deve ser carregada. A vantagem é que o histórico de alterações é conservada (sofre apenas uma renumeração).

Para fazer o dump, o comando é:

svnadmin dump d:\repoPathToMove > d:\backupFolder\repoName.dump

Para carregar o dump criado:

svnadmin load d:\pathToNewRepo --parent-dir InternalFolder < d:\backupFolder\repoName.dump

De notar a opção –parent-dir onde indico a pasta do repositório para onde os ficheiros devem ser carregados. É necessário criar a pasta no repositório de destino antes de efectuar o load. Caso contrário é levantado um erro e os dump não é carregado. Uma forma mais simpels de o escrever (e sem criar o ficheiro dump) é:

svnadmin dump d:\repoPathToMove | svnadmin load d:\pathToNewRepo --parent-dir InternalFolde

Less.js – Less em javascript!

June 20th, 2010

O Less é um framework de escrita de css que permite utilizar variáveis, herança e outros coisas giras. Mencionei-o há uns posts atrás, linkando as versões de ruby e .net. As versões que mencionei são construídos server-side. Actualemnte, o Less está a ser portado para javascript, permitindo a mesma funcioanlidade mas processado do lado do cliente. Este post refere essa evolução:

http://fadeyev.net/2010/06/19/lessjs-will-obsolete-css/

Outro framework do género a ver: Sass http://sass-lang.com/

Livros para trocas

June 6th, 2010

Tenho por aqui alguns livros que ou já li, ou que ainda não li mas também já não tenho grande interesse em manter por não ter perspectivas de ler. Na verdade, tenho acumulado alguns livros nas prateleiras que num momento ou noutro tinham interesse, mas neste momento não estou focados neles. Alguns nem comecei a ler (infelizmente). Uma questão de prioridades…

O que procuro é orientar outros livros que me possam interessar, seja para referencia, seja para explorar novos temas. Não estou muito preocupado com o estado impecável dos livros, se bem que quanto melhor, melhor. Procuro mais livros sobre algoritmia, padrões de código, e arquitectura, como também algumas tecnologias / linguagens como o Ruby, Processing, OpenFrameworks, ASP.NET MVC, Javascript, AJAX, Programação por objectos, Mecurial, Git, temas avançados de Web Services / WCF / SOA e C# e Asp.NET, DSLs, NHibernate, Code Generation, Silverlight, WPF, aspectos de engenharia de software, desenvolvimento de aplicações (processo e gestão) e até o “business side” das coisas…

Os que já não preciso:

  • Expert C# 2008 Business Objects

    Este livro é acerca da framework CLSA.NET – uma framework muito completa para desenvolvimento de aplicações, escrito pelo autor do projecto. Comprei na altura para puxar algumas ideias para a minha própria framework.Tirei algumas ideias que me interessavam na altura.

  • Interface-Oriented Design

    Sou fã dos livros da Pragmatic Bookshelf. Tenho alguns lidos e por ler, quer físicos quer ebooks. Este é interessante e discute abordagens de design de código recorrendo a interfaces.

  • The Object Oriented Thought Process

    Este foi lido com alguma rapidez e está interessante. Comprei este livro com o intuíto de ajudar aqui o pessoal a readquirir alguns dos conceitos de programação por objectos. A descrição podia ser melhor, especialmente nas questões básicas, Ainda assim, aborda com exemplos e algumas figuras a POO.

  • Visual Studio Tools for Office

    Num projecto, estava planeado desenvolver uns componentes para integrar com o Outlook, e na altura.. siga livros para estudar. Felizmente não foi necessário. De qualquer forma, neste caso, tinha acertado na versão errada do livro – Office 2003 – e portanto pode não servir para todos. De qualquer forma pode ser útil para alguém em termos e sistemas legados?

  • Outlook 2007 Programming

    idêntico ao anterior em termos de objectivo, mas este livro estava orientado À versão correcta. Tal como o anterior, estão novíssimos.

  • Iron Python In Action

    Este comprei no sentido de aprender uma nova linguagem. Preferencialmente uma dinâmica, para captar novos modos e abordagens a resolução de problemas e algoritmos, e o Python parecia-me bom candidato. Tinha na altura visto vídeos acerca do Iron Python em Silverlight e isso despertou-me. Infelizmente as prioridades impediram-me de ver este livro. Hoje preferia Iron Ruby, já que temos alguns projectos com o Ruby (normal) a decorrer. Mais um novíssimo…

  • SQL Server 2008 Administration in Action

    Mais uma associado a um caminho provável de um projecto que teria de estudar. Ja tinha conhecimentos com o 2005, mas ainda não conhecia as novidades do 2008, muito menos as ferramentas de gestão das mesmas. Está novíssimo, pois não foi necessário ver, e as prioridades, como sempre, fizeram-me escolher outros primeiro.

  • Quem tiver interesse num destes títulos, digam qualquer coisa. Ou se tiverem títulos interessantes, listam-nos cá nos comentários! :D

    Ruby Warrior

    June 4th, 2010

    Acabei há instantes o nível intermédio (o segundo dos dois níveis) do Ruby-Warrior, um jogo criado para ensinar inteligência artificial com base em Ruby. O jogo é muito simples e muito divertido, especialmente para quem é geek (lol) e gosta de programar. Aqui no escritório temos o desafio interno de terminar o jogo sem recorrer a ajudas externas. Felizmente consegui sem externas e quase sem usar as pistas que o jogo nos dá quando morremos.

    O jogo é muito simples – visualmente é texto na linha de comandos – e temos de mover a nossa personagem pelos diversos níveis da torre, para chegar ao topo e salvar a princesa. Pelo meio vamos encontrando diversos inimigos e pessoal que precisa de ser salvo. No jogo, programamos a nossa personagem de modo a tomar as decisões correctas para ultrapassar os inimigos e chegar à escadaria de cada nível.

    O modo iniciante é jogado apenas a uma dimensão (um eixo apenas) enquanto o segundo modo (intermédio) já decorre a duas dimensões tornando necessário implementar decisões de prioridade relativo a direcções em que devemos actuar. Assim, somos obrigados a analisar o meio envolvente e prioritizar as acções.

    O jogo é muito giro, e permite ensinar bastante acerca do Ruby, especialmente a definição de métodos e mecanismos de controlo (if /elsif /else e array e for). Também ajuda a descubrir formas de criar decisões em código.

    Recomendo vivamente experimentarem – o código pode ser puxado do GitHub do Ryan Bate. Tinha pensado colocar aqui o meu código, mas achei melhor não.. é melhor experimentar e descobrir. Se bem que posso partilhar a minha solução com quem quiser ver :D

    .Less (Dot Less) -> “CSS on Steroids”

    June 3rd, 2010

    Ainda no outro dia, falava com os meus colegas de como seria muito bom que o CSS tivesse estruturas de herança incorporados e variáveis.. especialmente variáveis, para não ter de definir constantemente as mesmas coisas.

    E hoje enquanto lia o blog do Spaanjaars, descubir o .Less, uma biblioteca dedicada a extender o CSS, introduzindo variáveis e esquemas de herança. Soa bem? Se soa!! :D

    Com o .Less, podemos escrever codigo com variáveis:

    @brand_color: #4D926F;
     
    #header {
      color: @brand_color;
    }
     
    h2 {
      color: @brand_color;
    }

    Ou nestings de selectores:

    #header {
        color: red;
        a {
           font-weight: bold;
           text-decoration: none;
        }
    }

    Ou misturas de elementos:

    .rounded_corners(@radius: 5px) {
      -moz-border-radius: @radius;
      -webkit-border-radius: @radius;
      border-radius: @radius;
    }
     
    #header {
      .rounded_corners;
    }
     
    #footer {
      .rounded_corners(10px);
    }

    Lindo, né? Originalmente, o Less foi construído para o Ruby (é um gem para o Ruby), mas um grupo converteu-o para .Net, criando o .Less. E ainda bem! No .Net, funciona como um HTTPHandler. Os ficheiros de CSS são ficheiros .less que são transformados aquando do pedido da página. É também possível usar templates T4 para criar versões estáticas do CSS.

    A experimentar!

    MyPaint – software de pintura open source

    May 25th, 2010

    Aqui vai um link para quem gosta de pintar – um software open-source (gratuito) dedicado a pintura, e que parece bastante interessante e que deverá agradar. É de experimentar, e está disponível para Windows e Linux.

    http://mypaint.intilinux.com/?page_id=9