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.
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
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);