5

When I attempt to display a Japanese string in a UILabel on iOS, it gets displayed using Chinese encoding instead of Japanese.

The two encodings are nearly identical, except in a few specific cases. For example, here is how the character 直 (Unicode U+76F4) is rendered in Chinese (top) vs. Japanese (bottom):

Chinese vs Japanese

(see here for more examples)

The only time Japanese strings render correctly is when the user's system locale is ja-jp (Japan), but I'd like it to render as Japanese for all users.

Is there any way to force the Japanese encoding? Android has TextView.TextLocale, but I don't see anything similar on iOS UILabel

(Same question for Android. I tagged this Swift/Objective-C because, although I'm looking for a Xamarin.iOS solution, the API is almost the same)

BlueRaja - Danny Pflughoeft
  • 84,206
  • 33
  • 197
  • 283
  • there are numerous existing questions on this topic - have you tried searching? https://www.google.com/search?q=uilabel+display+japanese+site:stackoverflow.com – Jason Feb 07 '22 at 03:36
  • @Jason: Yes, I've read the first 50 or so of those results. None of them are about Japanese vs Chinese encoding. The _only_ place I've seen this talked about is [here](https://developer.apple.com/forums/thread/61623), but no solution was found. – BlueRaja - Danny Pflughoeft Feb 07 '22 at 03:42
  • 1
    Please post the relevant code that you are using to display the label. And what is the locale of the device set to? – Jason Feb 07 '22 at 03:45
  • @Jason Neither of those things should be relevant. But: in Xamarin.Forms, it would be `` which gets translated to Xamarin.iOS `new UILabel { Text = "直" }` which gets translated to whatever the equivalent in Swift is. The expected user locale is `en-US` or similar, because this is a Japanese learning app. As mentioned, the unicode text is displayed _almost_ correctly, except that it uses Chinese encoding rather than Japanese encoding. – BlueRaja - Danny Pflughoeft Feb 07 '22 at 03:51
  • I tried the [approach here](https://stackoverflow.com/a/22061784/8187800) the first one is not working , I think you have to take the second one , refer to [Xamarin.iOS solution](https://learn.microsoft.com/en-us/xamarin/ios/app-fundamentals/localization/). – ColeX Feb 08 '22 at 07:16
  • Just add support of japanese localization. for japanese users system will display right symbol – Cy-4AH Feb 08 '22 at 11:29
  • @ColeX-MSFT The second approach (and second link) are about localizing strings, which is completely unrelated to what I'm asking. See update, I tried to make the question more clear. – BlueRaja - Danny Pflughoeft Feb 08 '22 at 20:57
  • i think set your app's font by swift code is simplest way. `UILabel.appearance().font = .custom("Hiragino Sans", size: 30)` – Quang Hà Feb 09 '22 at 07:45

2 Answers2

4

You just need to specify language identifier for attributed string, like

let label = UILabel()
let text = NSAttributedString(string: "直", attributes: [
    .languageIdentifier: "ja",                            // << this !! 
    .font: UIFont.systemFont(ofSize: 64)
])
label.attributedText = text

demo

Tested with Xcode 13.2 / iOS 15.2

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
2

I found an extremely hacky solution that seems to work. However, it seems absurd that there's no way to simply set the locale of a label, so if anyone finds something I missed, please post an answer.


The trick relies on the fact that the Hiragino font displays kanji using Japanese encoding rather than Chinese encoding by default. However, the font looks like shit for English text, so I have to search every string in every label for Japanese substrings and manually change the font using NSMutableAttributedString. The font is also completely broken so I had to find another workaround to fix that.

[assembly: ExportRenderer(typeof(Label), typeof(RingotanLabelRenderer))]
namespace MyApp
{
    public class MyLabelRenderer : LabelRenderer
    {
        private readonly UIFont HIRAGINO_FONT = UIFont.FromName("HiraginoSans-W6", 1); // Size gets updated later

        protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            // BUGFIX: Chinese encoding is shown by default. Switch to Hiragino font, which correctly shows Japanese characters
            // Taken from https://stackoverflow.com/a/71045204/238419
            if (Control?.Text != null && e.PropertyName == "Text")
            {
                var kanjiRanges = GetJapaneseRanges(Control.Text).ToList();
                if (kanjiRanges.Count > 0)
                {
                    var font = HIRAGINO_FONT.WithSize((nfloat)Element.FontSize);
                    var attributedString = Control.AttributedText == null
                        ? new NSMutableAttributedString(Control.Text)
                        : new NSMutableAttributedString(Control.AttributedText);

                    // Search through string for all instances of Japanese characters and update the font
                    foreach (var (start, end) in kanjiRanges)
                    {
                        int length = end - start + 1;
                        var range = new NSRange(start, length);
                        attributedString.AddAttribute(UIStringAttributeKey.Font, font, range);

                        // Bugfix: Hiragino font is broken (https://stackoverflow.com/a/44397572/238419) so needs to be adjusted upwards
                        // jesus christ Apple
                        attributedString.AddAttribute(UIStringAttributeKey.BaselineOffset, (NSNumber)(Element.FontSize/10), range);
                    }

                    Control.AttributedText = attributedString;
                }
            }
        }

        // Returns all (start,end) ranges in the string which contain only Japanese strings
        private IEnumerable<(int,int)> GetJapaneseRanges(string str)
        {
            for (int i = 0; i < str.Length; i++)
            {
                if (IsJapanese(str[i]))
                {
                    int start = i;
                    while (i < str.Length - 1 && KanjiHelper.IsJapanese(str[i]))
                    {
                        i++;
                    }
                    int end = i;
                    yield return (start, end);
                }
            }
        }

        private static bool IsJapanese(char character)
        {
            // An approximation. See https://github.com/caguiclajmg/WanaKanaSharp/blob/792f45a27d6e543d1b484d6825a9f22a803027fd/WanaKanaSharp/CharacterConstants.cs#L110-L118
            // for a more accurate version
            return character >= '\u3000' && character <= '\u9FFF' 
                || character >= '\uFF00';
        }
    }
}
BlueRaja - Danny Pflughoeft
  • 84,206
  • 33
  • 197
  • 283