From 545c4ad5f8101e304ecb8d4a074e5b3a3400446a Mon Sep 17 00:00:00 2001 From: Petros Douvantzis Date: Thu, 18 Sep 2025 18:48:11 +0300 Subject: [PATCH 1/2] Wrap application context The SDK can now wrap the application context. Sample app, readme and documentation have been updated. In the readme, we still propose to initialize the SDK in the `onCreate()` method. Introduced: `TxNative#isInitialized()` --- README.md | 37 +++++++++++++++ .../myapplication/MyApplication.java | 45 ++++++++++++------- .../myapplication/SimpleIntentService.java | 4 +- .../com/transifex/txnative/NativeCore.java | 2 +- .../java/com/transifex/txnative/TxNative.java | 29 +++++++----- 5 files changed, 88 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 435eaf4..d888df0 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,43 @@ Context wrappedContext = TxNative.wrap(getApplicationContext()); wrappedContext.getString(); ``` +If you want to wrap the application context itself, you need to move the SDK's initialization from the application's `onCreate()` to `attachBaseContext()`: + +```java + @Override + protected void attachBaseContext(Context base) { + // Initialize TxNative + String token = ""; + + LocaleState localeState = new LocaleState( + base, // Use the base context instead of getApplicationContext() + // source locale + "en", + // supported locales + new String[]{"en", "el", "de", "fr", "ar", "sl", "es_ES", "es_MX"}, + null); + + TxNative.init( + // application context + base, // Use the base context instead of getApplicationContext() + // a LocaleState instance + localeState, + // token + token, + // cdsHost URL + null, + // a TxCache implementation + null, + // a MissingPolicy implementation + new AndroidMissingPolicy()); + + // Wrap the base application context + super.attachBaseContext(TxNative.wrap(base)); +} +``` + +Note though that this global wrapper can interfere with third-party libraries that use their own string resources. In that case, use "AndroidMissingPolicy" so that these libraries have their strings translated. + If you want to disable the SDK functionality, don't initialize it and don't call any `TxNative` methods. `TxNative.wrap()` will be a no-op and the context will not be wrapped. Thus, all `getString()` etc methods, won't flow through the SDK. ## Cache diff --git a/TransifexNativeSDK/app/src/main/java/com/transifex/myapplication/MyApplication.java b/TransifexNativeSDK/app/src/main/java/com/transifex/myapplication/MyApplication.java index 5740a75..37d9730 100644 --- a/TransifexNativeSDK/app/src/main/java/com/transifex/myapplication/MyApplication.java +++ b/TransifexNativeSDK/app/src/main/java/com/transifex/myapplication/MyApplication.java @@ -1,6 +1,7 @@ package com.transifex.myapplication; import android.app.Application; +import android.content.Context; import android.content.Intent; import com.transifex.txnative.LocaleState; @@ -20,36 +21,50 @@ public void onCreate() { // https://stackoverflow.com/questions/55265834/change-locale-not-work-after-migrate-to-androidx/58004553#58004553 // AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); + // Start a service just for testing purposes + Intent serviceIntent = new Intent(this, SimpleIntentService.class); + SimpleIntentService.enqueueWork(this, serviceIntent); + + // Uncomment to use strings as served by Android prefixed with "test: " +// TxNative.setTestMode(true); + + // Uncomment, to disable styling of strings with HTML markup such as + // R.string.styled_text_not_escaped +// TxNative.setSupportSpannable(false); + + // Fetch all translations from CDS + TxNative.fetchTranslations(null, null); + } + + @Override + protected void attachBaseContext(Context base) { // Initialize TxNative String token = null; // The app locales entered here should match the ones in `resConfigs` in gradle, so that // multi locale support works for newer Androids. - LocaleState localeState = new LocaleState(getApplicationContext(), + LocaleState localeState = new LocaleState(base, "en", new String[]{"en", "el", "de", "fr", "ar", "sl"}, null); + TxNative.init( - getApplicationContext(), // application context + base, // application context localeState, // a LocaleState instance token, // token null, // cdsHost URL null, // a TxCache implementation null); // a MissingPolicy implementation - // Uncomment to use strings as served by Android prefixed with "test: " -// TxNative.setTestMode(true); - - // Uncomment, to disable styling of strings with HTML markup such as - // R.string.styled_text_not_escaped -// TxNative.setSupportSpannable(false); + // OPTIONAL: + // Wrap the application's base context to allow TxNative to intercept all string resource + // requests (e.g. from getApplicationContext().getString()). + // Warning: This global wrapper can interfere with third-party libraries that use their + // own string resources. Use "AndroidMissingPolicy" so that these libraries have their + // strings translated. + super.attachBaseContext(TxNative.wrap(base)); - // Fetch all translations from CDS - TxNative.fetchTranslations(null, null); - - // Start a service just for testing purposes - Intent serviceIntent = new Intent(this, SimpleIntentService.class); - SimpleIntentService.enqueueWork(this, serviceIntent); + // SAFER: Do not wrap the application's base context. + // super.attachBaseContext(base); } - } diff --git a/TransifexNativeSDK/app/src/main/java/com/transifex/myapplication/SimpleIntentService.java b/TransifexNativeSDK/app/src/main/java/com/transifex/myapplication/SimpleIntentService.java index 37f1c8d..cff8670 100644 --- a/TransifexNativeSDK/app/src/main/java/com/transifex/myapplication/SimpleIntentService.java +++ b/TransifexNativeSDK/app/src/main/java/com/transifex/myapplication/SimpleIntentService.java @@ -24,8 +24,8 @@ public static void enqueueWork(Context context, Intent work) { @Override protected void onHandleWork(@NonNull Intent intent) { - // Make sure that you use getBaseContext() and not getApplicationContext() - String success = getBaseContext().getResources().getString(R.string.success); + // Make sure that you do not use an app context via getApplicationContext().getString() + String success = getString(R.string.success); Log.d(TAG, "Service status: " + success); } diff --git a/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/NativeCore.java b/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/NativeCore.java index 16e4bc3..9b83516 100644 --- a/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/NativeCore.java +++ b/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/NativeCore.java @@ -65,7 +65,7 @@ public NativeCore(@NonNull Context applicationContext, @Nullable String cdsHost, @Nullable TxCache cache, @Nullable MissingPolicy missingPolicy) { - mContext = applicationContext.getApplicationContext(); + mContext = applicationContext; mMainHandler = new Handler(mContext.getMainLooper()); mLocaleState = localeState; mLocaleState.setCurrentLocaleListener(mCurrentLocaleListener); diff --git a/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/TxNative.java b/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/TxNative.java index 8b657aa..1accb88 100644 --- a/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/TxNative.java +++ b/TransifexNativeSDK/txsdk/src/main/java/com/transifex/txnative/TxNative.java @@ -29,9 +29,10 @@ public class TxNative { private static NativeCore sNativeCore = null; /** - * Initialize the SDK. + * Initialize the SDK. The method should only be called once. *

- * Should be called in {@link Application#onCreate()}. + * Should be called in {@link Application#onCreate()} or + * {@link Application#attachBaseContext(Context)}. *

* * @param applicationContext The application context. @@ -63,6 +64,16 @@ public static void init(@NonNull Context applicationContext, ViewPump.init(new TxInterceptor()); } + /** + * Checks if the SDK has been initialized by a previous call to + * {@link #init(Context, LocaleState, String, String, TxCache, MissingPolicy)}. + * + * @return true if the SDK has been initialized, false otherwise. + */ + public static boolean isInitialized() { + return sNativeCore != null; + } + /** * When test mode is enabled, TransifexNative functionality is disabled: the translations provided * by the SDK are not used. The original strings, as provided by Android's localization system, @@ -158,9 +169,8 @@ public static void fetchTranslations(@Nullable String localeCode) { *

* Warning: You should use getBaseContext(), instead of * getApplicationContext() when using string methods in services. - * - * @param context The activity context to wrap. + * @param context The context to wrap. * * @return The wrapped context. */ @@ -181,15 +191,9 @@ public static Context wrap(@NonNull Context context) { /** * Wraps a context to enable TransifexNative functionality in services and other scopes besides * activities. - *

- * Warning: You should use getBaseContext(), instead of - * getApplicationContext() when using string methods in services. - *

- * Check out the installation guide regarding the usage of this method. - * - * @param context The service context to wrap. * * @return The wrapped context. + * @deprecated Use {@link #wrap(Context)} instead. */ @Deprecated public static Context generalWrap(@NonNull Context context) { @@ -201,6 +205,9 @@ public static Context generalWrap(@NonNull Context context) { * that extends {@link androidx.appcompat.app.AppCompatActivity}. *

* This method should be called in {@link AppCompatActivity#getDelegate()}. + *

+ * If your activity is extending {@link com.transifex.txnative.activity.TxBaseAppCompatActivity TxBaseAppCompatActivity} + * you don't have to call this method. * * @param delegate The activity's AppCompatDelegate. * @param baseContext The activity's base context. From d3845b5bd71ed8e88a56ddc2c5368b45e4f0ec19 Mon Sep 17 00:00:00 2001 From: Petros Douvantzis Date: Fri, 19 Sep 2025 13:57:41 +0300 Subject: [PATCH 2/2] Update gradle.yml workflow Updated actions/upload-artifact: v3 to v4 --- .github/workflows/gradle.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 84e7f59..767e361 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -36,10 +36,11 @@ jobs: run: ./gradlew test - name: Upload test reports if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: unitTestReports path: | TransifexNativeSDK/clitool/build/reports/tests/test/ TransifexNativeSDK/common/build/reports/tests/test/ TransifexNativeSDK/txsdk/build/reports/tests/ + if-no-files-found: warn