fmtlib / fmt

A modern formatting library
https://fmt.dev
Other
19.84k stars 2.42k forks source link

fmt::sprintf(locale, ...) support - analog of snprintf_l() #3958

Closed eugenegff closed 1 month ago

eugenegff commented 1 month ago

fmtlib supports locale aware formatting, even with custom locales, but only in fmt::format(locale, ...) This patch adds locale support to fmt::printf() functions family

Our application, Live Home 3D, has large amount of localized strings, all with printf-like specifiers, and we successfully used snprintf_l on Mac, iOS, Windows, but Android surprised us. Android has very rudimentary support of locales in libc, only "C" and "UTF-8". Moreover, basic working horse function __vfprintf() uses nl_langinfo(RADIXCHAR) to obtain decimal_separator and nl_langinfo() does simply case RADIXCHAR: result = "."; break; There are no xxxprintf_l() functions at all. https://android.googlesource.com/platform/bionic/+/refs/heads/main/libc/stdio/vfprintf.cpp https://android.googlesource.com/platform/bionic/+/refs/heads/main/libc/bionic/langinfo.cpp

Following test passed on Windows, macOS and most importantly - on Android

TEST(printf_test, locale) {
    struct slash : std::numpunct<char> {
        char do_decimal_point() const { return '/'; }  // separate with slash
    };
    std::locale loc(std::cout.getloc(), new slash);

    EXPECT_EQ(fmt::format(loc, "The answer is {:.2f}", 42.0), "The answer is 42.00");
    EXPECT_EQ(fmt::format(loc, "The answer is {:.2Lf}", 42.0), "The answer is 42/00");
    EXPECT_EQ(fmt::sprintf(loc, "The answer is %.2f", 42.0), "The answer is 42/00");
}
vitaut commented 1 month ago

Thanks for the PR but I don't think we should extend the legacy printf API in any way. If you want to use C++ locales, I recommend migrating to fmt::format/std::format.

eugenegff commented 1 month ago

Thank you for fast response

Unfortunately, Android applications use printf-like format for native string resources, and that string resources system provides packaging, deployment of only required languages, plural forms handling, and yes, locale aware formatting - but only from the Java API. Changing printf-like format of string resources to anything else on this platform is just not practical, as all UI controls on Android are implemented in Java too.

<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>

// following line selects proper language, proper plural form, and performs locale aware formatting
// R.string.welcome_messages - is generated during build integer index into resources storage
String text = context.getString(R.string.welcome_messages, username, mailCount);

https://developer.android.com/guide/topics/resources/string-resource#FormattingAndStyling

Original non-Android Java also uses printf-like format https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.util.Locale-java.lang.String-java.lang.Object...-

But I understand, that fmtlib is used to promote new format syntax, and adding features to printf-like is not aligned with that goal :(