From 8593eee9fec5aa908b932e76ebfd0741af223bf5 Mon Sep 17 00:00:00 2001 From: Ruslan Shestopalyuk Date: Mon, 13 May 2024 22:45:04 -0700 Subject: [PATCH] Kotlinify I18nManagerModule (#44537) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/44537 # Changelog: [Internal] - As in the title. Reviewed By: NickGerleman Differential Revision: D57248159 fbshipit-source-id: 800de0454e4ac1bbb95679eaad6c24dc4d48ad22 --- .../ReactAndroid/api/ReactAndroid.api | 21 ++-- .../i18nmanager/I18nManagerModule.java | 61 ------------ .../modules/i18nmanager/I18nManagerModule.kt | 43 ++++++++ .../react/modules/i18nmanager/I18nUtil.java | 98 ------------------- .../react/modules/i18nmanager/I18nUtil.kt | 81 +++++++++++++++ .../uimanager/style/BorderRadiusStyle.kt | 2 +- 6 files changed, 138 insertions(+), 168 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nManagerModule.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nManagerModule.kt delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.kt diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index e1b6c52b4b122e..cf7577c46d4c3a 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -3329,7 +3329,7 @@ public class com/facebook/react/modules/fresco/SystraceRequestListener : com/fac public fun requiresExtraMap (Ljava/lang/String;)Z } -public class com/facebook/react/modules/i18nmanager/I18nManagerModule : com/facebook/fbreact/specs/NativeI18nManagerSpec { +public final class com/facebook/react/modules/i18nmanager/I18nManagerModule : com/facebook/fbreact/specs/NativeI18nManagerSpec { public fun (Lcom/facebook/react/bridge/ReactApplicationContext;)V public fun allowRTL (Z)V public fun forceRTL (Z)V @@ -3337,13 +3337,18 @@ public class com/facebook/react/modules/i18nmanager/I18nManagerModule : com/face public fun swapLeftAndRightInRTL (Z)V } -public class com/facebook/react/modules/i18nmanager/I18nUtil { - public fun allowRTL (Landroid/content/Context;Z)V - public fun doLeftAndRightSwapInRTL (Landroid/content/Context;)Z - public fun forceRTL (Landroid/content/Context;Z)V - public static fun getInstance ()Lcom/facebook/react/modules/i18nmanager/I18nUtil; - public fun isRTL (Landroid/content/Context;)Z - public fun swapLeftAndRightInRTL (Landroid/content/Context;Z)V +public final class com/facebook/react/modules/i18nmanager/I18nUtil { + public static final field Companion Lcom/facebook/react/modules/i18nmanager/I18nUtil$Companion; + public final fun allowRTL (Landroid/content/Context;Z)V + public final fun doLeftAndRightSwapInRTL (Landroid/content/Context;)Z + public final fun forceRTL (Landroid/content/Context;Z)V + public static final fun getInstance ()Lcom/facebook/react/modules/i18nmanager/I18nUtil; + public final fun isRTL (Landroid/content/Context;)Z + public final fun swapLeftAndRightInRTL (Landroid/content/Context;Z)V +} + +public final class com/facebook/react/modules/i18nmanager/I18nUtil$Companion { + public final fun getInstance ()Lcom/facebook/react/modules/i18nmanager/I18nUtil; } public final class com/facebook/react/modules/image/ImageLoaderModule : com/facebook/fbreact/specs/NativeImageLoaderAndroidSpec, com/facebook/react/bridge/LifecycleEventListener { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nManagerModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nManagerModule.java deleted file mode 100644 index 0ea640a63545a7..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nManagerModule.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.modules.i18nmanager; - -import android.content.Context; -import android.os.Build; -import com.facebook.fbreact.specs.NativeI18nManagerSpec; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.common.MapBuilder; -import com.facebook.react.module.annotations.ReactModule; -import java.util.Locale; -import java.util.Map; - -/** {@link NativeModule} that allows JS to set allowRTL and get isRTL status. */ -@ReactModule(name = NativeI18nManagerSpec.NAME) -public class I18nManagerModule extends NativeI18nManagerSpec { - private final I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance(); - - public I18nManagerModule(ReactApplicationContext context) { - super(context); - } - - @Override - public Map getTypedExportedConstants() { - final Context context = getReactApplicationContext(); - final Locale locale; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - locale = context.getResources().getConfiguration().getLocales().get(0); - } else { - locale = context.getResources().getConfiguration().locale; - } - - final Map constants = MapBuilder.newHashMap(); - constants.put("isRTL", sharedI18nUtilInstance.isRTL(context)); - constants.put( - "doLeftAndRightSwapInRTL", sharedI18nUtilInstance.doLeftAndRightSwapInRTL(context)); - constants.put("localeIdentifier", locale.toString()); - return constants; - } - - @Override - public void allowRTL(boolean value) { - sharedI18nUtilInstance.allowRTL(getReactApplicationContext(), value); - } - - @Override - public void forceRTL(boolean value) { - sharedI18nUtilInstance.forceRTL(getReactApplicationContext(), value); - } - - @Override - public void swapLeftAndRightInRTL(boolean value) { - sharedI18nUtilInstance.swapLeftAndRightInRTL(getReactApplicationContext(), value); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nManagerModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nManagerModule.kt new file mode 100644 index 00000000000000..4ce2355b289ea7 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nManagerModule.kt @@ -0,0 +1,43 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.modules.i18nmanager + +import android.os.Build +import com.facebook.fbreact.specs.NativeI18nManagerSpec +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.module.annotations.ReactModule + +/** [NativeModule] that allows JS to set allowRTL and get isRTL status. */ +@ReactModule(name = NativeI18nManagerSpec.NAME) +public class I18nManagerModule(context: ReactApplicationContext?) : NativeI18nManagerSpec(context) { + override public fun getTypedExportedConstants(): Map { + val context = getReactApplicationContext() + val locale = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + context.resources.configuration.locales[0] + } else { + @Suppress("DEPRECATION") context.resources.configuration.locale + } + return mapOf( + "isRTL" to I18nUtil.instance.isRTL(context), + "doLeftAndRightSwapInRTL" to I18nUtil.instance.doLeftAndRightSwapInRTL(context), + "localeIdentifier" to locale.toString()) + } + + override fun allowRTL(value: Boolean) { + I18nUtil.instance.allowRTL(getReactApplicationContext(), value) + } + + override fun forceRTL(value: Boolean) { + I18nUtil.instance.forceRTL(getReactApplicationContext(), value) + } + + override fun swapLeftAndRightInRTL(value: Boolean) { + I18nUtil.instance.swapLeftAndRightInRTL(getReactApplicationContext(), value) + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.java deleted file mode 100644 index db6d8052420c42..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.modules.i18nmanager; - -import android.content.Context; -import android.content.SharedPreferences; -import androidx.core.text.TextUtilsCompat; -import androidx.core.view.ViewCompat; -import java.util.Locale; - -public class I18nUtil { - private static I18nUtil sharedI18nUtilInstance = null; - - private static final String SHARED_PREFS_NAME = "com.facebook.react.modules.i18nmanager.I18nUtil"; - private static final String KEY_FOR_PREFS_ALLOWRTL = "RCTI18nUtil_allowRTL"; - private static final String KEY_FOR_PREFS_FORCERTL = "RCTI18nUtil_forceRTL"; - private static final String KEY_FOR_PERFS_MAKE_RTL_FLIP_LEFT_AND_RIGHT_STYLES = - "RCTI18nUtil_makeRTLFlipLeftAndRightStyles"; - - private I18nUtil() { - // Exists only to defeat instantiation. - } - - public static I18nUtil getInstance() { - if (sharedI18nUtilInstance == null) { - sharedI18nUtilInstance = new I18nUtil(); - } - return sharedI18nUtilInstance; - } - - /** - * Check if the device is currently running on an RTL locale. This only happens when the app: - * - *
    - *
  • is forcing RTL layout, regardless of the active language (for development purpose) - *
  • allows RTL layout when using RTL locale - *
- */ - public boolean isRTL(Context context) { - if (isRTLForced(context)) { - return true; - } - return isRTLAllowed(context) && isDevicePreferredLanguageRTL(); - } - - /** - * Should be used very early during app start up Before the bridge is initialized - * - * @return whether the app allows RTL layout, default is true - */ - private boolean isRTLAllowed(Context context) { - return isPrefSet(context, KEY_FOR_PREFS_ALLOWRTL, true); - } - - public void allowRTL(Context context, boolean allowRTL) { - setPref(context, KEY_FOR_PREFS_ALLOWRTL, allowRTL); - } - - public boolean doLeftAndRightSwapInRTL(Context context) { - return isPrefSet(context, KEY_FOR_PERFS_MAKE_RTL_FLIP_LEFT_AND_RIGHT_STYLES, true); - } - - public void swapLeftAndRightInRTL(Context context, boolean flip) { - setPref(context, KEY_FOR_PERFS_MAKE_RTL_FLIP_LEFT_AND_RIGHT_STYLES, flip); - } - - /** Could be used to test RTL layout with English Used for development and testing purpose */ - private boolean isRTLForced(Context context) { - return isPrefSet(context, KEY_FOR_PREFS_FORCERTL, false); - } - - public void forceRTL(Context context, boolean forceRTL) { - setPref(context, KEY_FOR_PREFS_FORCERTL, forceRTL); - } - - // Check if the current device language is RTL - private boolean isDevicePreferredLanguageRTL() { - final int directionality = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()); - return directionality == ViewCompat.LAYOUT_DIRECTION_RTL; - } - - private boolean isPrefSet(Context context, String key, boolean defaultValue) { - SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE); - return prefs.getBoolean(key, defaultValue); - } - - private void setPref(Context context, String key, boolean value) { - SharedPreferences.Editor editor = - context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE).edit(); - editor.putBoolean(key, value); - editor.apply(); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.kt new file mode 100644 index 00000000000000..dd42a4bc5fa501 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.modules.i18nmanager + +import android.content.Context +import androidx.core.text.TextUtilsCompat +import androidx.core.view.ViewCompat +import java.util.Locale + +public class I18nUtil private constructor() { + /** + * Check if the device is currently running on an RTL locale. This only happens when the app: + * * is forcing RTL layout, regardless of the active language (for development purpose) + * * allows RTL layout when using RTL locale + */ + public fun isRTL(context: Context): Boolean = + if (isRTLForced(context)) { + true + } else isRTLAllowed(context) && isDevicePreferredLanguageRTL + + /** + * Should be used very early during app start up Before the bridge is initialized + * + * @return whether the app allows RTL layout, default is true + */ + private fun isRTLAllowed(context: Context): Boolean = + isPrefSet(context, KEY_FOR_PREFS_ALLOWRTL, true) + + public fun allowRTL(context: Context, allowRTL: Boolean) { + setPref(context, KEY_FOR_PREFS_ALLOWRTL, allowRTL) + } + + public fun doLeftAndRightSwapInRTL(context: Context): Boolean = + isPrefSet(context, KEY_FOR_PERFS_MAKE_RTL_FLIP_LEFT_AND_RIGHT_STYLES, true) + + public fun swapLeftAndRightInRTL(context: Context, flip: Boolean) { + setPref(context, KEY_FOR_PERFS_MAKE_RTL_FLIP_LEFT_AND_RIGHT_STYLES, flip) + } + + /** Could be used to test RTL layout with English Used for development and testing purpose */ + private fun isRTLForced(context: Context): Boolean = + isPrefSet(context, KEY_FOR_PREFS_FORCERTL, false) + + public fun forceRTL(context: Context, forceRTL: Boolean) { + setPref(context, KEY_FOR_PREFS_FORCERTL, forceRTL) + } + + private val isDevicePreferredLanguageRTL: Boolean + // Check if the current device language is RTL + get() { + val directionality = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) + return directionality == ViewCompat.LAYOUT_DIRECTION_RTL + } + + private fun isPrefSet(context: Context, key: String, defaultValue: Boolean): Boolean = + context + .getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE) + .getBoolean(key, defaultValue) + + private fun setPref(context: Context, key: String, value: Boolean) { + val editor = context.getSharedPreferences(SHARED_PREFS_NAME, Context.MODE_PRIVATE).edit() + editor.putBoolean(key, value) + editor.apply() + } + + public companion object { + public val instance: I18nUtil = I18nUtil() + @JvmStatic get() = field + + private const val SHARED_PREFS_NAME = "com.facebook.react.modules.i18nmanager.I18nUtil" + private const val KEY_FOR_PREFS_ALLOWRTL = "RCTI18nUtil_allowRTL" + private const val KEY_FOR_PREFS_FORCERTL = "RCTI18nUtil_forceRTL" + private const val KEY_FOR_PERFS_MAKE_RTL_FLIP_LEFT_AND_RIGHT_STYLES = + "RCTI18nUtil_makeRTLFlipLeftAndRightStyles" + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/BorderRadiusStyle.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/BorderRadiusStyle.kt index 0f475b4a327d22..8bbe6328d5b335 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/BorderRadiusStyle.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/style/BorderRadiusStyle.kt @@ -121,7 +121,7 @@ public data class BorderRadiusStyle( bottomRight = bottomRight?.resolve(width, height) ?: 0f, ) LayoutDirection.RTL -> - if (I18nUtil.getInstance().doLeftAndRightSwapInRTL(context)) { + if (I18nUtil.instance.doLeftAndRightSwapInRTL(context)) { ComputedBorderRadius( topLeft = topRight?.resolve(width, height) ?: 0f, topRight = topLeft?.resolve(width, height) ?: 0f,