Localization in Swift.


(Zhao Xin) #1

I encountered a localization problem today. At first I translated a string
like this.

let count = 10

let says = NSLocalizedString("It runs \(count) times", comment: "run
times")

I couldn't get the translation taking effect.

So I open the setting "Localization Debugging" in scheme and get this error:

[strings] ERROR, It runs 10 times not found in table Localizable of bundle

CFBundle 0x100c01c40 ... (executable, loaded)
IT RUNS 10 TIMES

I began to realize that` \(count)` was not dealed well in localization. The
compiler calculated the full string then looking for the translation,
instead of looking for the translation first.

I managed to replace my code with

let newSays = String.localizedStringWithFormat(NSLocalizedString("It runs

%d times", comment: "new run times"), count)

However, I still thing it would be better if we could use \(foo) directly,
as it is more Swift style. Any idea why this can't happen?

Zhaoxin


(Brent Royal-Gordon) #2

I managed to replace my code with

let newSays = String.localizedStringWithFormat(NSLocalizedString("It runs %d times", comment: "new run times"), count)

However, I still thing it would be better if we could use \(foo) directly, as it is more Swift style. Any idea why this can't happen?

I have some code that does that (Swift 2 version: <https://gist.github.com/brentdax/79fa038c0af0cafb52dd>), but it relies on the ExpressibleByStringInterpolation protocol, which is currently deprecated because it's due for a redesign.

Ultimately, localization is a Foundation-level concern. I'd love to see a future Foundation do something clever with NSLocalizedString, but it seems like their hands have been full with higher-priority stuff like the value-type equivalents.

···

--
Brent Royal-Gordon
Architechies


(Jens Alfke) #3

NSLocalizedString was designed (in the 1990s) to be used with methods like String(format:…) that take printf-style “%”-substituted format strings.
Swift’s string interpolation is obviously a different mechanism entirely.

I suspect that Swift interpolation won’t work well for localized strings because the string and the code are so tightly connected. Localization very often needs to change the order of parameters, for instance. It’s also unclear where things like number formatting happen in Swift interpolation; when localizing a string, the conversion needs to be done using the same locale as the string lookup, which might not happen if the string-to-number conversion is separate and uses the default locale.

—Jens

···

On Nov 1, 2016, at 1:53 AM, Zhao Xin via swift-users <swift-users@swift.org> wrote:

I began to realize that` \(count)` was not dealed well in localization. The compiler calculated the full string then looking for the translation, instead of looking for the translation first.


(Zhao Xin) #4

I think in Swift.

let count = 10

let says = NSLocalizedString("
​blabla
\(count)
​blabla
", comment: "
​blabla
")

​can be roughly interpreted as

let says = String.localizedStringWithFormat(NSLocalizedString("

​blabla
%
​@
​blabla
", comment: "
​blabla
"), count)

​So if Swift does not want to do much effort on this ​, it could just find
every localized string that is with '\(foo)' in `NSLocalizedString` and
converted to `String.localizedStringWithFormat(NSLocalizedString...`
internally.

Zhaoxin

···

On Wed, Nov 2, 2016 at 12:08 AM, Jens Alfke <jens@mooseyard.com> wrote:

> On Nov 1, 2016, at 1:53 AM, Zhao Xin via swift-users < > swift-users@swift.org> wrote:
>
> I began to realize that` \(count)` was not dealed well in localization.
The compiler calculated the full string then looking for the translation,
instead of looking for the translation first.

NSLocalizedString was designed (in the 1990s) to be used with methods like
String(format:…) that take printf-style “%”-substituted format strings.
Swift’s string interpolation is obviously a different mechanism entirely.

I suspect that Swift interpolation won’t work well for localized strings
because the string and the code are so tightly connected. Localization very
often needs to change the order of parameters, for instance. It’s also
unclear where things like number formatting happen in Swift interpolation;
when localizing a string, the conversion needs to be done using the same
locale as the string lookup, which might not happen if the string-to-number
conversion is separate and uses the default locale.

—Jens


(Zhao Xin) #5

The second should be

let says = String.localizedStringWithFormat(NSLocalizedString("

​blabla
%
​@
​blabla
", comment: "
​blabla
"),
​String(​
count
​)​
)

​Zhaoxin​

···

On Wed, Nov 2, 2016 at 10:50 AM, Zhao Xin <owenzx@gmail.com> wrote:

I think in Swift.

let count = 10

let says = NSLocalizedString("
​blabla
\(count)
​blabla
", comment: "
​blabla
")

​can be roughly interpreted as

let says = String.localizedStringWithFormat(NSLocalizedString("

​blabla
%
​@
​blabla
", comment: "
​blabla
"), count)

​So if Swift does not want to do much effort on this ​, it could just find
every localized string that is with '\(foo)' in `NSLocalizedString` and
converted to `String.localizedStringWithFormat(NSLocalizedString...`
internally.

Zhaoxin

On Wed, Nov 2, 2016 at 12:08 AM, Jens Alfke <jens@mooseyard.com> wrote:

> On Nov 1, 2016, at 1:53 AM, Zhao Xin via swift-users < >> swift-users@swift.org> wrote:
>
> I began to realize that` \(count)` was not dealed well in localization.
The compiler calculated the full string then looking for the translation,
instead of looking for the translation first.

NSLocalizedString was designed (in the 1990s) to be used with methods
like String(format:…) that take printf-style “%”-substituted format strings.
Swift’s string interpolation is obviously a different mechanism entirely.

I suspect that Swift interpolation won’t work well for localized strings
because the string and the code are so tightly connected. Localization very
often needs to change the order of parameters, for instance. It’s also
unclear where things like number formatting happen in Swift interpolation;
when localizing a string, the conversion needs to be done using the same
locale as the string lookup, which might not happen if the string-to-number
conversion is separate and uses the default locale.

—Jens


(Jens Alfke) #6

That’s not quite right, because the conversion of `count` to a string won’t be localized. Some languages use non-ASCII digits, use characters other than “.” for decimal points, etc. Depending on the exact type of `count`, it would need to be something like:

  String.localizedStringWithFormat(NSLocalizedString("​blabla%​d​blabla", comment: "​blabla"), ​count​)​

Hardcoding this specific kind of transformation into the parser seems like a bad idea, since NSLocalizedString isn’t part of the language. Why don’t you just use localizedStringWithFormat instead of string interpolation in your code?

—Jens

···

On Nov 1, 2016, at 7:52 PM, Zhao Xin <owenzx@gmail.com> wrote:

The second should be

let says = String.localizedStringWithFormat(NSLocalizedString("​blabla%​@​blabla", comment: "​blabla"), ​String(​count​)​)


(Zhao Xin) #7

I has known that, and it is the developer's choice, which means, the
developer has already known that. There are many circumstances that there
are no needs to translate at all. For example, if I want show the user that
I have to ask him to give me permission of a folder, the `url.path` has no
need to translate.

And of course,
` String.localizedStringWithFormat(NSLocalizedString("​blabla%​@​blabla",
comment: "​blabla"), ​` has already allows to do that. I just think it
should be in a more Swift way by using `\(foo)`.

Zhaoxin

···

On Wed, Nov 2, 2016 at 11:45 AM, Jens Alfke <jens@mooseyard.com> wrote:

On Nov 1, 2016, at 7:52 PM, Zhao Xin <owenzx@gmail.com> wrote:

The second should be

let says = String.localizedStringWithFormat(NSLocalizedString("

​blabla
%
​@
​blabla
", comment: "
​blabla
"),
​String(​
count
​)​
)

That’s not quite right, because the conversion of `count` to a string
won’t be localized. Some languages use non-ASCII digits, use characters
other than “.” for decimal points, etc. Depending on the exact type of
`count`, it would need to be something like:

String.localizedStringWithFormat(NSLocalizedString("​blabla%​d​blabla",
comment: "​blabla"), ​count​)​

Hardcoding this specific kind of transformation into the parser seems like
a bad idea, since NSLocalizedString isn’t part of the language. Why don’t
you just use localizedStringWithFormat instead of string interpolation in
your code?

—Jens


(Jens Alfke) #8

We’re getting off-topic, but paths do need to be translated, at least on Mac systems. The names of many standard folders like “Applications” and “Documents” are hardwired to English in the filesystem but are localized in the UI. Some application names get localized too (there’s a table in the app’s Info.plist that can substitute localized names.)

Anyway, string interpolation is convenient, but I wouldn’t say it should be the only way to format strings in Swift; it’s a lot less flexible than the C-style “%” substitutions. For comparison, even though C++’s iostreams use “<<“ to format strings by concatenation, I still end up using “%” based formatting a lot, depending on the use case.

—Jens

···

On Nov 1, 2016, at 10:40 PM, Zhao Xin <owenzx@gmail.com> wrote:

For example, if I want show the user that I have to ask him to give me permission of a folder, the `url.path` has no need to translate.


(Dave Abrahams) #9

I'm actually working on design in this area right now.

%-style formatting has the following drawbacks

- for anyone who doesn't use them regularly they are cryptic and
  complex, as the printf (3) man page attests.

- the spelling of these placeholders must match up to the types of the
  arguments, in the right order, or the behavior is undefined. Some
  limited support for compile-time checking of this correspondence could
  be implemented, but only for the cases where the format string is a
  literal.

- there's no reasonable way to extend the formatting vocabulary to cover
  the needs of new types: you are stuck with what's in the box.

In my opinion, we can and must do much better for Swift. If there's
something about “%” formatting that you particularly value, I'd like to
know about it, so I can make sure it's accomodated.

Thanks,

···

on Tue Nov 01 2016, Jens Alfke <swift-users-AT-swift.org> wrote:

On Nov 1, 2016, at 10:40 PM, Zhao Xin <owenzx@gmail.com> wrote:

For example, if I want show the user that I have to ask him to give me permission of a folder, the

`url.path` has no need to translate.

We’re getting off-topic, but paths do need to be translated, at least
on Mac systems. The names of many standard folders like “Applications”
and “Documents” are hardwired to English in the filesystem but are
localized in the UI. Some application names get localized too (there’s
a table in the app’s Info.plist that can substitute localized names.)

Anyway, string interpolation is convenient, but I wouldn’t say it
should be the only way to format strings in Swift; it’s a lot less
flexible than the C-style “%” substitutions. For comparison, even
though C++’s iostreams use “<<“ to format strings by concatenation, I
still end up using “%” based formatting a lot, depending on the use
case.

--
-Dave


(Jens Alfke) #10

It offers more control over formatting, like min/max widths, number base, decimal places, etc. Yes, you can do this in the code inside the interpolated string, but IMHO it’s awkward because it requires knowing a bunch of extra methods for string conversion, truncation, etc. It’s a lot easier for me to remember and type “%x” than it is to remember and type the method that converts an int to a hex string.

Also (and more importantly for localization) the formatting details are part of the localizable format string, not hardwired. One example of this is formatting currency, where a US localization would use “$%.2f” but other currencies might call for more or fewer decimal places. There are other examples where one might swap format strings for other purposes like different-width layouts for monospaced/terminal output.

There’s also a nonstandard extension used by Cocoa/CF’s formatters, that allows the parameters to be reordered. (I haven’t used it so I don’t know the syntax offhand.) This is of course important for localization, to follow a language’s grammar.

I think these features could be added to interpolation. Just as a quick idea, maybe a syntax that allows formatting metacharacters to be added at the start of the interpolation, like “Please pay $\((.2) total)” where the “(.2) specifies two decimal places, or “The address is \((x) addr)”.

—Jens

···

On Nov 2, 2016, at 12:50 PM, Dave Abrahams via swift-users <swift-users@swift.org> wrote:

In my opinion, we can and must do much better for Swift. If there's
something about “%” formatting that you particularly value, I'd like to
know about it, so I can make sure it's accomodated.


(Zhao Xin) #11

I am not talking to eliminate "%" style function. I am talking to add more
compatibility to `NSLocalizedString` with `\(foo)` style. As there is no
rule forbidding that, it should work. If someone doesn't need the flexible
parts, why he has to use the complicated way?

Zhaoxin

···

On Wed, Nov 2, 2016 at 1:49 PM, Jens Alfke <jens@mooseyard.com> wrote:

> On Nov 1, 2016, at 10:40 PM, Zhao Xin <owenzx@gmail.com> wrote:
>
> For example, if I want show the user that I have to ask him to give me
permission of a folder, the `url.path` has no need to translate.

We’re getting off-topic, but paths do need to be translated, at least on
Mac systems. The names of many standard folders like “Applications” and
“Documents” are hardwired to English in the filesystem but are localized in
the UI. Some application names get localized too (there’s a table in the
app’s Info.plist that can substitute localized names.)

Anyway, string interpolation is convenient, but I wouldn’t say it should
be the only way to format strings in Swift; it’s a lot less flexible than
the C-style “%” substitutions. For comparison, even though C++’s iostreams
use “<<“ to format strings by concatenation, I still end up using “%” based
formatting a lot, depending on the use case.

—Jens


(Jens Alfke) #12

I don’t think the ExpressibleByStringInterpolation protocol provides enough information to make this work. It hands the implementation a list of values to concatenate, some of which are strings, but as far as I can tell there’s no way to tell which of those strings are the pieces of the string literal and which of them are the results of expressions. So NSLocalizedString would not be able to reassemble the string template that you gave it, to look up in the localization table.

If I’m wrong about this, show me a workable implementation of it. :slight_smile:

Also, ExpressibleByStringInterpolation is marked as being deprecated and will be “replaced or redesigned in Swift 4.0.” Maybe to solve this limitation?

—Jens

···

On Nov 1, 2016, at 10:56 PM, Zhao Xin <owenzx@gmail.com> wrote:

I am not talking to eliminate "%" style function. I am talking to add more compatibility to `NSLocalizedString` with `\(foo)` style.


(Brent Royal-Gordon) #13

(Resending something that was accidentally off-list.)

I don’t think the ExpressibleByStringInterpolation protocol provides enough information to make this work. It hands the implementation a list of values to concatenate, some of which are strings, but as far as I can tell there’s no way to tell which of those strings are the pieces of the string literal and which of them are the results of expressions.

There's actually a simple trick. The even-indexed elements are literal strings; the odd-indexed ones are interpolated values. This is true even if you have two interpolations adjacent to each other—there will be an empty string between them. I've used this for a few different things, including a LocalizableString type in Swift 2 and a SQLStatement type in Swift 3.

  https://gist.github.com/brentdax/79fa038c0af0cafb52dd
  https://github.com/brentdax/swift-sql/blob/master/Sources/SQLStatement.swift

If I’m wrong about this, show me a workable implementation of it. :slight_smile:

See above. :^)

Also, ExpressibleByStringInterpolation is marked as being deprecated and will be “replaced or redesigned in Swift 4.0.” Maybe to solve this limitation?

I believe that making it easier to treat the literal and interpolated segments differently is one of the goals.

···

On Nov 1, 2016, at 11:09 PM, Jens Alfke via swift-users <swift-users@swift.org> wrote:

--
Brent Royal-Gordon
Architechies


(Zhao Xin) #14

I have already give a workable implementation above.

let count = 10

let says = NSLocalizedString("
​blabla
\(count)
​blabla
", comment: "
​blabla
")

​to

let says =

String.localizedStringWithFormat(NSLocalizedString("​blabla%​@​blabla",
comment: "​blabla"), ​String(​count​)​

When encounter​ `NSLocalizedString`, Swift looks into its key, if it
​s key​
contains \(foo), just replace it as
`String.localizedStringWithFormat(NSLocalizedString("​blabla%​@​blabla",
comment: "​blabla"), ​String(​foo​)​`. If not, keep it unchanged.

That is enough.

In current situation, Swift still first calculates the value of string
first, then `NSLocalizedString` work. So it just needs to add a little work
to check if there is any `\(foo)` in the string, instead of calculating
the value of string.

Zhaoxin

···

On Wed, Nov 2, 2016 at 2:09 PM, Jens Alfke <jens@mooseyard.com> wrote:

> On Nov 1, 2016, at 10:56 PM, Zhao Xin <owenzx@gmail.com> wrote:
>
> I am not talking to eliminate "%" style function. I am talking to add
more compatibility to `NSLocalizedString` with `\(foo)` style.

I don’t think the ExpressibleByStringInterpolation protocol provides
enough information to make this work. It hands the implementation a list of
values to concatenate, some of which are strings, but as far as I can tell
there’s no way to tell which of those strings are the pieces of the string
literal and which of them are the results of expressions. So
NSLocalizedString would not be able to reassemble the string template that
you gave it, to look up in the localization table.

If I’m wrong about this, show me a workable implementation of it. :slight_smile:

Also, ExpressibleByStringInterpolation is marked as being deprecated and
will be “replaced or redesigned in Swift 4.0.” Maybe to solve this
limitation?

—Jens


(Dave Abrahams) #15

In my opinion, we can and must do much better for Swift. If there's
something about “%” formatting that you particularly value, I'd like to
know about it, so I can make sure it's accomodated.

It offers more control over formatting, like min/max widths, number
base, decimal places, etc. Yes, you can do this in the code inside the
interpolated string, but IMHO it’s awkward because it requires knowing
a bunch of extra methods for string conversion, truncation, etc. It’s
a lot easier for me to remember and type “%x” than it is to remember
and type the method that converts an int to a hex string.

In my view this should look like

  "... \(x.format(radix: 16, width: 12))... "

Where the possible arguments to format() are statically known to the
compiler (and code completion!) based on the type of x.

Also (and more importantly for localization) the formatting details
are part of the localizable format string, not hardwired. One example
of this is formatting currency, where a US localization would use
“$%.2f” but other currencies might call for more or fewer decimal
places.

Yep, I'm paying attention to that, thanks.

There are other examples where one might swap format strings for other
purposes like different-width layouts for monospaced/terminal output.

I think we can leverage the same mechanisms used for localization to
handle those.

There’s also a nonstandard extension used by Cocoa/CF’s formatters,
that allows the parameters to be reordered. (I haven’t used it so I
don’t know the syntax offhand.) This is of course important for
localization, to follow a language’s grammar.

Right, that's crucial.

I think these features could be added to interpolation. Just as a
quick idea, maybe a syntax that allows formatting metacharacters to be
added at the start of the interpolation, like “Please pay $\((.2)
total)” where the “(.2) specifies two decimal places, or “The address
is \((x) addr)”.

I think the “.format(...)” approach is better, but it's equally
important that there are sufficient outside-the-Swift-source knobs for
localizers to add language-specific formatting parameters.

···

on Wed Nov 02 2016, Jens Alfke <jens-AT-mooseyard.com> wrote:

On Nov 2, 2016, at 12:50 PM, Dave Abrahams via swift-users <swift-users@swift.org> wrote:

--
-Dave


(Marco S Hyman) #16

Your implementation assume \(x) is always %@x. What does it do when given -- to use an example from the swift book:

  "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)”

···

On Nov 1, 2016, at 11:42 PM, Zhao Xin via swift-users <swift-users@swift.org> wrote:

I have already give a workable implementation above.


(Jens Alfke) #17

It’s also not an implementation. It’s just an English-language sketch for how you might do it. “Implementation” means actual code. As programmers we all know that there are tons of details that don’t become apparent until you try to build a real program, and both Marco and I have pointed some out.

—Jens

···

On Nov 2, 2016, at 11:23 AM, Marco S Hyman via swift-users <swift-users@swift.org> wrote:

On Nov 1, 2016, at 11:42 PM, Zhao Xin via swift-users <swift-users@swift.org> wrote:

I have already give a workable implementation above.

Your implementation assume \(x) is always %@x.


(Zhao Xin) #18

Hello everyone. Thanks to you all for replies in this thread.

I am currently working on a Xcode Extension for this purpose. I would like
to bring it to github in this week. This will be my first Xcode extension,
also my first github open sourced project.

Zhaoxin

···

On Thu, Nov 3, 2016 at 6:14 AM, Dave Abrahams via swift-users < swift-users@swift.org> wrote:

on Wed Nov 02 2016, Jens Alfke <jens-AT-mooseyard.com> wrote:

>> On Nov 2, 2016, at 12:50 PM, Dave Abrahams via swift-users < > swift-users@swift.org> wrote:
>>
>> In my opinion, we can and must do much better for Swift. If there's
>> something about “%” formatting that you particularly value, I'd like to
>> know about it, so I can make sure it's accomodated.
>
> It offers more control over formatting, like min/max widths, number
> base, decimal places, etc. Yes, you can do this in the code inside the
> interpolated string, but IMHO it’s awkward because it requires knowing
> a bunch of extra methods for string conversion, truncation, etc. It’s
> a lot easier for me to remember and type “%x” than it is to remember
> and type the method that converts an int to a hex string.

In my view this should look like

  "... \(x.format(radix: 16, width: 12))... "

Where the possible arguments to format() are statically known to the
compiler (and code completion!) based on the type of x.

>
> Also (and more importantly for localization) the formatting details
> are part of the localizable format string, not hardwired. One example
> of this is formatting currency, where a US localization would use
> “$%.2f” but other currencies might call for more or fewer decimal
> places.

Yep, I'm paying attention to that, thanks.

> There are other examples where one might swap format strings for other
> purposes like different-width layouts for monospaced/terminal output.

I think we can leverage the same mechanisms used for localization to
handle those.

> There’s also a nonstandard extension used by Cocoa/CF’s formatters,
> that allows the parameters to be reordered. (I haven’t used it so I
> don’t know the syntax offhand.) This is of course important for
> localization, to follow a language’s grammar.

Right, that's crucial.

> I think these features could be added to interpolation. Just as a
> quick idea, maybe a syntax that allows formatting metacharacters to be
> added at the start of the interpolation, like “Please pay $\((.2)
> total)” where the “(.2) specifies two decimal places, or “The address
> is \((x) addr)”.

I think the “.format(...)” approach is better, but it's equally
important that there are sufficient outside-the-Swift-source knobs for
localizers to add language-specific formatting parameters.

--
-Dave
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Zhao Xin) #19

I just uploaded this as a Xcode extension. You can download it here
<https://github.com/owenzhao/Localization-Helper>.
As long as Swift is not supporting this, you can use my Xcode extension.

Zhaoxin

···

On Thu, Nov 3, 2016 at 10:36 AM, Zhao Xin <owenzx@gmail.com> wrote:

Hello everyone. Thanks to you all for replies in this thread.

I am currently working on a Xcode Extension for this purpose. I would like
to bring it to github in this week. This will be my first Xcode extension,
also my first github open sourced project.

Zhaoxin

On Thu, Nov 3, 2016 at 6:14 AM, Dave Abrahams via swift-users < > swift-users@swift.org> wrote:

on Wed Nov 02 2016, Jens Alfke <jens-AT-mooseyard.com> wrote:

>> On Nov 2, 2016, at 12:50 PM, Dave Abrahams via swift-users < >> swift-users@swift.org> wrote:
>>
>> In my opinion, we can and must do much better for Swift. If there's
>> something about “%” formatting that you particularly value, I'd like to
>> know about it, so I can make sure it's accomodated.
>
> It offers more control over formatting, like min/max widths, number
> base, decimal places, etc. Yes, you can do this in the code inside the
> interpolated string, but IMHO it’s awkward because it requires knowing
> a bunch of extra methods for string conversion, truncation, etc. It’s
> a lot easier for me to remember and type “%x” than it is to remember
> and type the method that converts an int to a hex string.

In my view this should look like

  "... \(x.format(radix: 16, width: 12))... "

Where the possible arguments to format() are statically known to the
compiler (and code completion!) based on the type of x.

>
> Also (and more importantly for localization) the formatting details
> are part of the localizable format string, not hardwired. One example
> of this is formatting currency, where a US localization would use
> “$%.2f” but other currencies might call for more or fewer decimal
> places.

Yep, I'm paying attention to that, thanks.

> There are other examples where one might swap format strings for other
> purposes like different-width layouts for monospaced/terminal output.

I think we can leverage the same mechanisms used for localization to
handle those.

> There’s also a nonstandard extension used by Cocoa/CF’s formatters,
> that allows the parameters to be reordered. (I haven’t used it so I
> don’t know the syntax offhand.) This is of course important for
> localization, to follow a language’s grammar.

Right, that's crucial.

> I think these features could be added to interpolation. Just as a
> quick idea, maybe a syntax that allows formatting metacharacters to be
> added at the start of the interpolation, like “Please pay $\((.2)
> total)” where the “(.2) specifies two decimal places, or “The address
> is \((x) addr)”.

I think the “.format(...)” approach is better, but it's equally
important that there are sufficient outside-the-Swift-source knobs for
localizers to add language-specific formatting parameters.

--
-Dave
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users