Escenario

Tenemos un sitio de SharePoint 2010 o 2013 y queremos localizarlo en función del idioma del usuario que visita dicho sitio. Para ello tenemos diversas opciones.

  • Localización de Página Maestra (Masterpage) y Diseños de Página (Page Layouts)
  • Localización de recursos en Interfaz de Usuario (UI) en un desarrollo
  • Localización de recursos en código manejado en un desarrollo

Aunque en realidad, se puede resumir en, localización en Interfaz de Usuario y localización en código manejado.

 

Solución

0.- Creación del fichero de recursos

El fichero, deberá tener una estructura como la indicada bajo estas líneas, además de tener la extensión .resx. Si no os fiáis mucho y dado que este ejemplo lo he sacado de SharePoint 2013, podéis ir a la carpeta

 

<?xml version="1.0" encoding="utf-8"?>
<!-- _lcid="1033" _version="15.0.4420.1017" _dal="1" -->
<!-- _LocalBinding -->
<root>
  <!-- 
    Microsoft ResX Schema 
    Version 2.0
    The primary goals of this format is to allow a simple XML format 
    that is mostly human readable. The generation and parsing of the 
    various data types are done through the TypeConverter classes 
    associated with the data types.
    Example:
    ... ado.net/XML headers & schema ...
    <resheader name="resmimetype">text/microsoft-resx</resheader>
    <resheader name="version">2.0</resheader>
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>[base64 mime encoded serialized .NET Framework object]</value>
    </data>
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
        <comment>This is a comment</comment>
    </data>
    There are any number of "resheader" rows that contain simple 
    name/value pairs.
    Each data row contains a name, and value. The row also contains a 
    type or mimetype. Type corresponds to a .NET class that support 
    text/value conversion through the TypeConverter architecture. 
    Classes that don't support this are serialized and stored with the 
    mimetype set.
    The mimetype is used for serialized objects, and tells the 
    ResXResourceReader how to depersist the object. This is currently not 
    extensible. For a given mimetype the value must be set accordingly:
    Note - application/x-microsoft.net.object.binary.base64 is the format 
    that the ResXResourceWriter will generate, however the reader can 
    read any of the formats listed below.
    mimetype: application/x-microsoft.net.object.binary.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
            : and then encoded with base64 encoding.
    mimetype: application/x-microsoft.net.object.soap.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
            : and then encoded with base64 encoding.
    mimetype: application/x-microsoft.net.object.bytearray.base64
    value   : The object must be serialized into a byte array 
            : using a System.ComponentModel.TypeConverter
            : and then encoded with base64 encoding.
    -->
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
 
  <data name="myResource_1" xml:space="preserve">
    <value>Contact</value>
  </data>
 
  <data name="myResource_2" xml:space="preserve">
    <value>Contact</value>
  </data>
 
  <data name="myResource_3" xml:space="preserve">
    <value>Contact</value>
  </data>
 
...
...
...
 
  <data name="myResource_N" xml:space="preserve">
    <value>Contact</value>
  </data>
 
 
</root>

 

1.- Localización en Interfaz de Usuario

Se nos presentan varios posibles casos dependiendo del entorno y del alcance que queramos tener:

  • Localizar sólo los recursos en Página Maestra y Diseños de Página

En este caso, tan sólo tendremos que realizar dos acciones, crear el fichero de recursos que, básicamente es un XML, y agregarlo al directorio de recursos globales de la aplicación que se encuentra en directorio virtual de la aplicación web (Web Application) en la que queramos aplicar la localización, en la dirección C:inetpubwwwrootwssVirtualDirectoriesTU_APLICACION_WEBApp_GlobalResources. Posteriormente, en los elementos donde necesitemos las traducciones, añadir el contenido mediante una llamada al fichero de recursos. Por ejemplo:

<asp:Literal runat="server" Text="<%$Resources:nombre_del_fichero_de_recurso,nombre_del_recurso%>" />

 

  • Localizar sólo los recursos en Página Maestra y Diseños de Página para todas las aplicaciones web que se creen de ahora en adelante

El caso, es prácticamente el mismo que el del punto anterior, sólo que el fichero lo pondremos en la carpeta:

  • SharePoint 2010: C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions14CONFIGResources
  • SharePoint 2013: C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions15CONFIGResources

De esta forma, cada vez que creemos una nueva aplicación web de SharePoint, los ficheros de recursos que pongamos aquí serán copiados a la carpeta que especifiqué App_GlobalResources en el punto anterior.

  • Localizar los recursos en elementos visuales de una solución desarrollada desde Visual Studio

La idea es básicamente la misma, pero se resuelve de una forma más simple. En la solución, tendríamos que añadir la Carpeta Mapeada a los recursos globales de la aplicación, tal y como se ilustra en la imagen.

GlobalResources

Una vez añadida la carpeta, podemos crear un fichero de recursos y añadirle los elementos que necesitemos o, si ya lo habíamos creado, ponerlo ahí.

Para usarlo, sencillamente haremos lo mismo que anteriormente:

<asp:Literal runat="server" Text="<%$Resources:nombre_del_fichero_de_recurso,nombre_del_recurso%>" />
  • SharePoint 2013: Para SharePoint 2013 Apps se puede hacer de una forma un poco diferente tal y como se comenta en este artículo de la MSDN dado que SharePoint 2013 cambia el contexto de programación, dependiendo más de desarrollo web.

 

2.- Localización en código manejado

Aquí tenemos otras dos posibilidades:

  • Localizar los recursos globalmente para todos los sitios y desarrollos

Tendríamos que crear el fichero de recursos y colocarlo en la carpeta siguiente:

  • SharePoint 2010: C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions14Resources
  • SharePoint 2013: C:Program FilesCommon FilesMicrosoft SharedWeb Server Extensions15Resources

El uso de los recursos se hará de la siguiente forma:

SPUtility.GetLocalizedString("$Resources:Nombre_del_recurso", "Nombre_del_fichero_de_recursos", language);
  • Localizar los recursos desde una solución desarrollada desde Visual Studio

Al igual que en el caso visual, tendremos que añadir una Carpeta Mapeada en la solución que estemos desarrollando, en este caso será Local Resources

LocalResources

Una vez añadida la carpeta, podemos crear un fichero de recursos y añadirle los elementos que necesitemos o, si ya lo habíamos creado, ponerlo ahí.

Para usarlo, sencillamente haremos lo mismo que anteriormente:

SPUtility.GetLocalizedString("$Resources:Nombre_del_fichero_de_recursos, Nombre_del_recurso", "GloablSiteResources", language);