¿Qué es NSLocalizedString equivalente en Swift?

228

¿Hay un equivalente rápido de NSLocalizedString(...)? En Objective-C, usualmente usamos:

NSString *string = NSLocalizedString(@"key", @"comment");

¿Cómo puedo lograr lo mismo en Swift? Encontré una función:

func NSLocalizedString(
    key: String,
    tableName: String? = default,
    bundle: NSBundle = default,
    value: String = default,
    #comment: String) -> String

Sin embargo, es muy largo y no es conveniente en absoluto.

RaffAl
fuente
2
Lo mejor es crear una versión más corta del fragmento de código: NSLocalizedString ("", comment: "") ... Me gustó la solución de extensión, pero el problema es que genstrings no capturará estas cadenas en el archivo de traducción.
Matej Ukmar
3
En Swift 3, puede usar NSLocalizedString("Cancel", comment: "Cancel button title")aprovechando los valores predeterminados. Es conveniente, creo.
LShi
Este es un muy buen artículo sobre localización (extensión de cadena, tablas de cadenas diferentes e incluso pluralización): medium.com/@marcosantadev/…
LightMan
Este es un muy buen artículo sobre localización en Swift para una arquitectura robusta medium.com/@mendibarouk/…
Mendy

Respuestas:

373

Yo uso la siguiente solución:

1) crear extensión:

extension String {
    var localized: String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }
}

2) en el archivo Localizable.strings :

"Hi" = "Привет";

3) ejemplo de uso:

myLabel.text = "Hi".localized

¡disfrutar! ;)

--upd: -

para el caso con comentarios puede usar esta solución:

1) Extensión:

extension String {
    func localized(withComment:String) -> String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: withComment)
    }
}

2) en el archivo .strings:

/* with !!! */
"Hi" = "Привет!!!";

3) usando:

myLabel.text = "Hi".localized(withComment: "with !!!")
dr OX
fuente
92
El único problema con esto es que no podrá utilizar la genstringsutilidad para generar sus archivos .strings.
Ned
99
Esa es una muy buena idea! También lo hice un poco más inteligente al cambiarlo para func localized(comment: String = "") -> Stringque sea más pequeño y con comentarios opcionales :)
Gui Moura
2
¿Alguna idea de cómo usar genstringsesto?
Chris
48
Todos están muy entusiasmados con esta respuesta, pero el GRAN problema (para cualquier proyecto serio con varios idiomas) es que esto arruina completamente la administración de sus mensajes traducidos, porque genstringssolo funciona en cadenas literales pasadas a NSLocalizedString. Con esta solución inteligente, pierde la capacidad de actualizar sus archivos .strings usando la genstringsherramienta, y al menos para mí eso significa que no podré usar este enfoque simplificado.
Erik van der Neut
14
Encontré esta gran solución implementada en github.com/marmelroy/Localize-Swift . El problema de genstrings también se resuelve mediante un script de python personalizado incluido por el autor. No soy un autor
Tomek Cejner
279

El NSLocalizedStringexiste también en el mundo de Swift.

func NSLocalizedString(
    key: String,
    tableName: String? = default,
    bundle: NSBundle = default,
    value: String = default,
    #comment: String) -> String

El tableName, bundley valuelos parámetros están marcados con una defaultpalabra clave que significa que podemos omitir estos parámetros al llamar a la función. En este caso, se utilizarán sus valores predeterminados.

Esto lleva a la conclusión de que la llamada al método se puede simplificar para:

NSLocalizedString("key", comment: "comment")

Swift 5 : sin cambios, todavía funciona así.

RaffAl
fuente
44
La única diferencia es que el comentario no puede ser nulo, y el autocompletado está lejos de ser intuitivo para la versión corta.
Marcin
1
esto ya no funciona Me da error al decir que no se utilizan suficientes argumentos.
Aplicaciones 4 U
2
No es que lo anterior sea correcto en Xcode 6.3, Swift 1.2 con el cambio específico del objetivo-c, el comentario (como dijo Marcin) no puede ser nulo, pero puede ser "" (vacío).
Neil
2
Un comentario nulo / vacío dificulta la reubicación de la cadena más adelante en el archivo de cadena; si nada más agregue el nombre de la clase / archivo donde se usa como comentario.
Johan
Esta es la respuesta correcta. Una vez que Apple lo actualice para Swift, Xcode podrá convertir automáticamente esta API a su nueva API Swift y nada más se romperá. Incluso en el menú Refractor de Xcode actualmente (v 11.4.1) hay una Wrap in NSLocalizedStringopción que facilita las cosas simplemente resaltando texto, haciendo clic derecho y seleccionando el elemento del menú.
Ethan Allen
28

Una variación de las respuestas existentes:

Swift 5.1:

extension String {

    func localized(withComment comment: String? = nil) -> String {
        return NSLocalizedString(self, comment: comment ?? "")
    }

}

Luego puede usarlo simplemente con o sin comentarios:

"Goodbye".localized()
"Hello".localized(withComment: "Simple greeting")

Tenga en cuenta que genstringsno funcionará con esta solución.

José
fuente
14

Al usar de esta manera, es posible crear una implementación diferente para diferentes tipos (es decir, clases Int o personalizadas como CurrencyUnit, ...). También es posible escanear para invocar este método utilizando la utilidad genstrings. Simplemente agregue la bandera de rutina al comando

genstrings MyCoolApp/Views/SomeView.swift -s localize -o .

extensión:

import UIKit

extension String {
    public static func localize(key: String, comment: String) -> String {
        return NSLocalizedString(key, comment: comment)
    }
}

uso:

String.localize("foo.bar", comment: "Foo Bar Comment :)")
Kay
fuente
¡Esta respuesta es increíble y debería votarse más! Esta es la solución más simple que he encontrado hasta ahora si está buscando evitar traer otra biblioteca. Esta es una buena solución nativa.
cgossain
6

Versión Swift 3:) ...

import Foundation

extension String {
    var localized: String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }
}
ene
fuente
6

En realidad, puede usar dos fases para traducir sus textos en proyectos Swift:

1) La primera fase es usar la antigua forma de crear todas sus cadenas traducibles:

NSLocalisedString("Text to translate", comment: "Comment to comment")

1.1) Entonces deberías usar genstrings para generar Localizable.strings:

$ genstrings *swift

2) Después, debes usar esta respuesta .

2.1) Use su opción XCode "Buscar y reemplazar" basada en la expresión regular. En cuanto al ejemplo dado (si no tiene comentarios) la expresión regular será:

NSLocalizedString\((.*)\, comment:\ \"\"\) 

y reemplazarlo con

$1.localized

o (si tienes comentarios)

NSLocalizedString\((.*)\, comment:\ (.*)\)

y reemplazarlo con

$1.localizedWithComment(comment: $2)

Puedes jugar con expresiones regulares y diferentes combinaciones de extensiones como desees. La forma general es dividir todo el proceso en dos fases. Espero que ayude.

GYFK
fuente
1
Lo siento, no entiendo muchas respuestas aquí. ¿Cuál es el beneficio del método sobre el uso NSLocalizedString("Cancel", comment: "Cancel button title")?
LShi
1
@LShi algunas personas se quejaban, eso NSLocalizedStringparece menos rápido de lo que debería parecer. String.localizedPor otro lado, parece más Swifty, pero no puede usar la gesntringsutilidad que se usa comúnmente para facilitar su trabajo con la internacionalización. Mi punto es que es bastante fácil mezclar ambos enfoques. Entonces, principalmente es una cuestión de legibilidad.
GYFK
¿Qué sucede si necesitas hacer otra ronda genstrings? ¿Reemplazas todo .localizedpor NSLocalizedString?
Cristik
5

Creó un pequeño método auxiliar para casos, donde el "comentario" siempre se ignora. Menos código es más fácil de leer:

public func NSLocalizedString(key: String) -> String {
    return NSLocalizedString(key, comment: "")
}

Simplemente colóquelo en cualquier lugar (fuera de una clase) y Xcode encontrará este método global.

JOM
fuente
12
Esta es una mala práctica. Los comentarios son recomendables y útiles a menos que esté haciendo toda la traducción usted mismo.
Jeremías
Incluso si se está traduciendo, los comentarios serían útiles, especialmente en un proyecto grande.
cuña
4

Probablemente la mejor manera es esta aquí .

fileprivate func NSLocalizedString(_ key: String) -> String {
    return NSLocalizedString(key, comment: "")
}

y

import Foundation
extension String {
    static let Hello = NSLocalizedString("Hello")
    static let ThisApplicationIsCreated = NSLocalizedString("This application is created by the swifting.io team")
    static let OpsNoFeature = NSLocalizedString("Ops! It looks like this feature haven't been implemented yet :(!")
}

entonces puedes usarlo así

let message: String = .ThisApplicationIsCreated
print(message)

para mí esto es lo mejor porque

  • Las cadenas codificadas están en un archivo específico, por lo que el día que desea cambiarlo es realmente fácil
  • Más fácil de usar que escribir manualmente las cadenas en su archivo cada vez
  • las cadenas gens seguirán funcionando
  • puede agregar más extensiones, como una por controlador de vista para mantener las cosas ordenadas
Robin Dorpe
fuente
3
Lo que hay que tener en cuenta es que las cadenas definidas de la manera descrita son cadenas estáticas. La aplicación debe reiniciarse después de cambiar el idioma en la aplicación de configuración de iOS. Si no, reinícielo usted mismo para ver los cambios. También puede tener una sobrecarga de memoria, ya que inicializamos todas las cadenas a la vez, no en el momento en que son necesarias.
iDevAmit
2
Creo que es mejor usar propiedades calculadas aquí, como estastatic var Hello: String = { return NSLocalizedString("Hello") }
art-of-dreams
Votado en contra porque no sigue las pautas de nomenclatura
Cristik
3

Cuando estás desarrollando un SDK. Necesitas una operación extra.

1) cree Localizable.strings como de costumbre en YourLocalizeDemoSDK.

2) cree las mismas cadenas Localizable.string en YourLocalizeDemo.

3) encuentre su ruta de paquete de YourLocalizeDemoSDK.

Swift4 :

// if you use NSLocalizeString in NSObject, you can use it like this
let value = NSLocalizedString("key", tableName: nil, bundle: Bundle(for: type(of: self)), value: "", comment: "")

Bundle(for: type(of: self))le ayuda a encontrar el paquete en YourLocalizeDemoSDK. Si usa Bundle.mainen su lugar, obtendrá un valor incorrecto (de hecho, será la misma cadena con la clave).

Pero si desea utilizar la extensión de cadena mencionada por el Dr. OX . Necesitas hacer un poco más. La extensión de origen se ve así.

extension String {
    var localized: String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }
}

Como sabemos, estamos desarrollando un SDK, Bundle.mainobtendremos el paquete del paquete de YourLocalizeDemo. Eso no es lo que queremos. Necesitamos el paquete en YourLocalizeDemoSDK. Este es un truco para encontrarlo rápidamente.

Ejecute el código a continuación en una instancia de NSObject en YourLocalizeDemoSDK. Y obtendrá la URL de YourLocalizeDemoSDK.

let bundleURLOfSDK = Bundle(for: type(of: self)).bundleURL
let mainBundleURL = Bundle.main.bundleURL

Imprima las dos URL, verá que podemos construir la base bundleURLofSDK en mainBundleURL. En este caso, será:

let bundle = Bundle(url: Bundle.main.bundleURL.appendingPathComponent("Frameworks").appendingPathComponent("YourLocalizeDemoSDK.framework")) ?? Bundle.main

Y la extensión de la cadena será:

extension String {
    var localized: String {
        let bundle = Bundle(url: Bundle.main.bundleURL.appendingPathComponent("Frameworks").appendingPathComponent("YourLocalizeDemoSDK.framework")) ?? Bundle.main
        return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "")
    }
}

Espero eso ayude.

Liam
fuente
2

He creado mi propia herramienta Genstrings para extraer cadenas usando una función de traducción personalizada

extension String {

    func localizedWith(comment:String) -> String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: comment)
    }

}

https://gist.github.com/Maxdw/e9e89af731ae6c6b8d85f5fa60ba848c

Analizará todos sus archivos rápidos y exportará las cadenas y comentarios en su código a un archivo .strings.

Probablemente no sea la forma más fácil de hacerlo, pero es posible.

Max
fuente
1

Aunque esto no responde al problema del acortamiento, pero me ayudó a organizar los mensajes, creé una estructura para los mensajes de error como a continuación

struct Constants {
    // Error Messages
    struct ErrorMessages {
        static let unKnownError = NSLocalizedString("Unknown Error", comment: "Unknown Error Occured")
        static let downloadError = NSLocalizedString("Error in Download", comment: "Error in Download")
    }
}

let error = Constants.ErrorMessages.unKnownError

De esta manera, puede organizar los mensajes y hacer que funcionen las cadenas gens.

Y este es el comando genstrings utilizado

find ./ -name \*.swift -print0 | xargs -0 genstrings -o .en.lproj
anoop4real
fuente
1

Útil para el uso en pruebas unitarias:

Esta es una versión simple que se puede extender a diferentes casos de uso (por ejemplo, con el uso de tableNames).

public func NSLocalizedString(key: String, referenceClass: AnyClass, comment: String = "") -> String 
{
    let bundle = NSBundle(forClass: referenceClass)
    return NSLocalizedString(key, tableName:nil, bundle: bundle, comment: comment)
}

Úselo así:

NSLocalizedString("YOUR-KEY", referenceClass: self)

O como este con un comentario:

NSLocalizedString("YOUR-KEY", referenceClass: self, comment: "usage description")
GatoCurioso
fuente
1
Es una mala práctica dejar de lado los comentarios.
José
@ José Gracias por tu comentario. El código fue pensado como una idea, no como una plantilla para copiar y pegar. Pero agregué la opción de agregar comentarios si lo desea;)
GatoCurioso
1

Esta es una mejora en el enfoque ".localized". Comience agregando la extensión de clase, ya que esto ayudará con las cadenas que estaba configurando programáticamente:

extension String {
    func localized (bundle: Bundle = .main, tableName: String = "Localizable") -> String {
        return NSLocalizedString(self, tableName: tableName, value: "\(self)", comment: "")
    }
}

Ejemplo de uso para cadenas que establezca mediante programación:

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

Ahora los archivos de traducción del guión gráfico de Xcode hacen que el administrador de archivos sea desordenado y tampoco manejan bien las actualizaciones del guión gráfico. Un mejor enfoque es crear una nueva clase de etiqueta básica y asignarla a todas las etiquetas de su guión gráfico:

class BasicLabel: UILabel {
    //initWithFrame to init view from code
    override init(frame: CGRect) {
      super.init(frame: frame)
      setupView()
    }

    //initWithCode to init view from xib or storyboard
    required init?(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      setupView()
    }

    //common func to init our view
    private func setupView() {
        let storyboardText = self.text
        text = storyboardText?.localized()
    }
}

Ahora, cada etiqueta que agregue y proporcione el valor predeterminado predeterminado en el guión gráfico se traducirá automáticamente, suponiendo que haya proporcionado una traducción.

Podrías hacer lo mismo para UIButton:

class BasicBtn: UIButton {
    //initWithFrame to init view from code
    override init(frame: CGRect) {
      super.init(frame: frame)
      setupView()
    }

    //initWithCode to init view from xib or storyboard
    required init?(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      setupView()
    }

    //common func to init our view
    private func setupView() {
        let storyboardText = self.titleLabel?.text
        let lclTxt = storyboardText?.localized()
        setTitle(lclTxt, for: .normal)
    }
}
Dave G
fuente
0

Cuando traduzca, diga del inglés, donde una frase es la misma, a otro idioma donde sea diferente (debido al género, las conjugaciones verbales o la declinación), la forma más simple de NSString en Swift que funciona en todos los casos son los tres argumentos. . Por ejemplo, la frase en inglés "anterior era", se traduce de manera diferente al ruso para el caso de "peso" ("предыдущ ий был") y para "cintura" ("предыдущ ая был а ").

En este caso, necesita dos traducciones diferentes para una Fuente (en términos de la herramienta XLIFF recomendada en WWDC 2018). No puede lograrlo con dos argumentos NSLocalizedString, donde "anterior era" será el mismo tanto para la "clave" como para la traducción al inglés (es decir, para el valor). La única forma es usar la forma de tres argumentos

NSLocalizedString("previousWasFeminine", value: "previous was", comment: "previousWasFeminine")

NSLocalizedString("previousWasMasculine", value: "previous was", comment: "previousWasMasculine")

donde las claves ("previousWasFeminine" y "previousWasMasculine") son diferentes.

Sé que el consejo general es traducir la frase como un todo, sin embargo, a veces es demasiado lento e inconveniente.

Vadim Motorine
fuente
-1

Localización con idioma predeterminado:

extension String {
func localized() -> String {
       let defaultLanguage = "en"
       let path = Bundle.main.path(forResource: defaultLanguage, ofType: "lproj")
       let bundle = Bundle(path: path!)

       return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
    }
}
investigador
fuente