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';
}
}
}