Giter VIP home page Giter VIP logo

philology's Introduction

Philology

CircleCI Download ko-fi

An easy way to dynamically replace Strings of your Android App or provide new languages Over-the-air without needed to publish a new release on Google Play.

Why should I be interested in Philology

How String resources work on Android?

Android Resources provide us with an easy way to internationalise our App: a file with all the strings used by our App and a copy of it for every language the App is translated to. Android OS does the rest choosing the proper file depending on device's language.

That is perfect and I don't want to stop using it.

The problem

These strings are hardcoded inside our App. If there's a typo or you find a better way to express something, a new version of the App needs to be deployed to include the newer translation. This is a slow process and a poor user experience. We all know users take their time to update an app (if they ever do so) and there's also the time Google Play takes to make a new version of an app available to all users.

How Philology solves this problem?

Philology doesn't replace the way you are using resources in your current Android Development. Instead, it improves the process by intercepting the value returned from your hardcoded translation files inside of the app and check if there is a newer value in the server. This allows for typo fixing, better wording or even for adding a new language. All in real time, without releasing a new version of the App.

With Philology you could replace hardcoded texts instantly and win time before the new release is done.

Getting Started

Dependency

Philology use ViewPump library to intercept the view inflate process and reword strings resources. It allows you to use other libraries like Calligraphy that intercept the view inflate process

dependencies {
    compile 'com.jcminarro:Philology:2.1.0'
    compile 'io.github.inflationx:viewpump:2.0.3'
}

Usage

Initialize Philology and ViewPump

Define your PhilologyRepositoryFactory who provides PhilologyRepository according with the selected Locale on the device. Kotlin:

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        // Init Philology with our PhilologyRepositoryFactory
        Philology.init(MyPhilologyRepositoryFactory)
        // Add PhilologyInterceptor to ViewPump
        // If you are already using Calligraphy you can add both interceptors, there is no problem
        ViewPump.init(ViewPump.builder().addInterceptor(PhilologyInterceptor).build())
    }
}

object MyPhilologyRepositoryFactory : PhilologyRepositoryFactory {
    override fun getPhilologyRepository(locale: Locale): PhilologyRepository? = when (locale.language) {
        Locale.ENGLISH.language -> EnglishPhilologyRepository
        Locale("es", "ES").language -> SpanishPhilologyRepository
        // If we don't support a language we could return null as PhilologyRepository and
        // values from the strings resources file will be used
        else -> null
    }
}

object EnglishPhilologyRepository : PhilologyRepository {
    override fun getText(key: String): CharSequence? = when (key) {
        "label" -> "New value for the `label` key, it could be fetched from a database or an external API server"
        // If we don't want reword a strings we could return null and the value from the string resources file will be used
        else -> null
    }

    override fun getPlural(key: String, quantityString: String): CharSequence?  = when ("${key}_$quantityString") {
        "plurals_label_one" -> "New value for the `plurals_label` key and `one` quantity keyword"
        "plurals_label_other" -> "New value for the `plurals_label` key and `other` quantity keyword"
        // If we don't want reword a plural we could return null and the value from the string resources file will be used
        else -> null
    }

   override fun getTextArray(key: String): Array<CharSequence>? = when (key) {
        "days" -> arrayOf(
            "Monday",
            "Tuesday",
            "Wednesday",
            "Thursday",
            "Friday",
            "Saturday",
            "Saturday"
        )
        // If we don't want reword a string array we could return null and the value from the string resources file will be used
        else -> null
    }
}

object SpanishPhilologyRepository : PhilologyRepository { /* Implementation */ }

Java:

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        PhilologyRepositoryFactory repositoryFactory = new MyPhilologyRepositoryFactory();
        Philology.INSTANCE.init(repositoryFactory);
        ViewPump.init(ViewPump.builder().addInterceptor(PhilologyInterceptor.INSTANCE).build());
    }
}

public class MyPhilologyRepositoryFactory extends PhilologyRepositoryFactory {
    @Nullable
    @Override
    public PhilologyRepository getPhilologyRepository(@NotNull Locale locale) {
        if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) {
            return new EnglishPhilologyRepository();
        }
        return null;
    }
}

public class EnglishPhilologyRepository extends PhilologyRepository {
    @Nullable
    @Override
    public CharSequence getText(@NotNull Resource resource) { /* Implementation */}
}

Inject into Context

Wrap the Activity Context. Kotlin:

class BaseActivity : AppCompatActivity() {
    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(ViewPumpContextWrapper.wrap(Philology.wrap(newBase)))
    }
}

Java:

public class BaseActivity extends AppCompatActivity {
    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(ViewPumpContextWrapper.wrap(Philology.INSTANCE.wrap(newBase)));
    }
}

That is all

CustomViews

Philology allows you to reword your own CustomViews, the only that you need to do is provide an implementation of ViewTransformer that rewords the text fields used by your CustomView. The Context used to inflate your CustomView is already wrapped by the library, so, you can assign the @StringRes used on the .xml file that will be provided on the reword() method.

Kotlin:

object MyCustomViewTransformer : ViewTransformer {
    override fun reword(view: View, attributeSet: AttributeSet): View = view.apply {
        when (this) {
            is MyCustomView -> reword(attributeSet)
        }
    }

    private fun MyCustomView.reword(attributeSet: AttributeSet) {
        @StringRes val textResId = context.getStringResourceId(attributeSet, R.styleable.MyCustomView_text)
        if (textResId > 0) setTextToMyCustomView(textResId)
    }
}

Java:

public class MyCustomViewTransformer extends ViewTransformer {
    private static String MY_CUSTOM_ATTRIBUTE_NAME = "text";
    @NotNull
    @Override
    public View reword(@NotNull View view, @NotNull AttributeSet attributeSet) {
        if (view instanceof MyCustomView) {
            MyCustomView myCustomView = (MyCustomView) view;
            int textResId = getStringResourceId(myCustomView.getContext(), attributeSet, R.styleable.MyCustomView_text);
            if (textResId > 0) {
                myCustomView.setTextToMyCustomView(textResId);
            }
        }
        return view;
    }
}

After you implement your ViewTransformer you need to provide it to Philology injecting a ViewTransformerFactory by the init() method Kotlin:

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        Philology.init(MyPhilologyRepositoryFactory, MyViewTransformerFactory)
        ViewPump.init(ViewPump.builder().addInterceptor(PhilologyInterceptor.INSTANCE).build());
    }
}

object MyViewTransformerFactory : ViewTransformerFactory{
    override fun getViewTransformer(view: View): ViewTransformer = when (view) {
        is MyCustomView -> MyCustomViewTransformer
        else -> null
    }
}

Java:

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        PhilologyRepositoryFactory repositoryFactory = new MyPhilologyRepositoryFactory();
        ViewTransformerFactory viewTransformerFactory = new MyCustomViewTransformerFactory();
        Philology.INSTANCE.init(repositoryFactory, viewTransformerFactory);
        ViewPump.init(ViewPump.builder().addInterceptor(PhilologyInterceptor.INSTANCE).build());
    }
}

public class MyCustomViewTransformerFactory implements ViewTransformerFactory {
    @Nullable
    @Override
    public ViewTransformer getViewTransformer(@NotNull View view) {
        if (view instanceof MyCustomView) {
            return new MyViewTransformer();
        }
        return null;
    }
}

Do you want to contribute?

Feel free to add any useful feature to the library, we will be glad to improve it with your help. I'd love to hear about your use case too, especially if it's not covered perfectly.

Developed By

Follow me on Twitter Add me to Linkedin Follow me on Twitter

License

Copyright 2018 Jc Miñarro

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

philology's People

Contributors

antercepter avatar ffelini avatar ilya-gh avatar jcminarro avatar karn avatar mbircu avatar pr0t3us avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

philology's Issues

java.lang.RuntimeException: Unable to instantiate receiver cn.***.MyReceiver: java.lang.ClassCastException: io.github.inflationx.viewpump.ViewPumpContextWrapper cannot be cast to android.app.ContextImpl

at android.app.ActivityThread.handleReceiver(ActivityThread.java:3351)
at android.app.ActivityThread.-wrap17(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1812)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:192)
at android.app.ActivityThread.main(ActivityThread.java:6771)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:825)
Caused by: java.lang.ClassCastException: io.github.inflationx.viewpump.ViewPumpContextWrapper cannot be cast to android.app.ContextImpl
at android.app.ActivityThread.handleReceiver(ActivityThread.java:3338)
at android.app.ActivityThread.-wrap17(Unknown Source:0) 
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1812) 
at android.os.Handler.dispatchMessage(Handler.java:106) 
at android.os.Looper.loop(Looper.java:192) 
at android.app.ActivityThread.main(ActivityThread.java:6771) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:825) 

TypedArray#getString does not return a translated string

Describe the bug
When reading a custom attribute for a custom view component we are using the TypedArray returned by Context#obtainStyledAttributes. This function is invoked on a PhilologyContextWrapper provided by Philology#wrap.

The current issue with this TypedArray instance is that its getString() function does not return a translated text. That's why we need the ViewTransformer#reword where we basically obtain the resource id of the String and reapply it on the view. And that's why the android framework also fails to set a translated text from a custom attribute(we've added the support in #27 but it's still handle this manually). This is handled currently by transformer package implementations for various components.

To Reproduce
Replace this reword implementation with:

    private fun TextView.reword(attributeSet: AttributeSet) {
        @StringRes val text =
            context.obtainStyledAttributes(attributeSet, intArrayOf(android.R.attr.text)).getString(0)
        @StringRes val hint =
            context.obtainStyledAttributes(attributeSet, intArrayOf(android.R.attr.hint)).getString(0)

        if (text.isNotEmpty()) setText(text)
        if (hint.isNotEmpty) setHint(hint)
    }

Run the sample and see the TextView components missing translations.
Expected behavior
The ideal case would be to help the framework use a correct TypedArray instance containing translated strings. This would help us get rid of ViewTransformer.reword, but this is the ideal case.

In order to ease the life of other teams would be nice to have a lint rule that will prohibit the usage of the TypedArray#getString() letting devs now that it will not return a translated text.
This should be a temporary solution while we search for a real fix.

Library version
v-2.1.0

NPE on `updateConfiguration()` method

updateConfiguration() method is called from the parent constructor, so our baseResources is not initialized yet and we get a NPE using it

 Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.content.res.Resources.updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics)' on a null object reference
        at com.jcminarro.philology.PhilologyResources.updateConfiguration(PhilologyResources.kt:65)
        at android.content.res.Resources.<init>(Resources.java:257)
        at android.content.res.Resources.<init>(Resources.java:234)
        at com.jcminarro.philology.PhilologyResources.<init>(PhilologyResources.kt:25)
        at com.jcminarro.philology.PhilologyContextWrapper$res$2.invoke(PhilologyContextWrapper.kt:17)
        at com.jcminarro.philology.PhilologyContextWrapper$res$2.invoke(PhilologyContextWrapper.kt:11)
        at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
        at com.jcminarro.philology.PhilologyContextWrapper.getRes(PhilologyContextWrapper.kt)
        at com.jcminarro.philology.PhilologyContextWrapper.getResources(PhilologyContextWrapper.kt:21)
        at android.content.ContextWrapper.getResources(ContextWrapper.java:86)
        at android.content.ContextWrapper.getResources(ContextWrapper.java:86)
        at android.view.ContextThemeWrapper.getResources(ContextThemeWrapper.java:74)
        at androidx.appcompat.app.AppCompatActivity.getResources(AppCompatActivity.java:543)
        at android.view.Window.getDefaultFeatures(Window.java:1306)
        at android.view.Window.<init>(Window.java:453)
        at com.android.internal.policy.impl.PhoneWindow.<init>(PhoneWindow.java:290)
        at com.android.internal.policy.impl.Policy.makeNewWindow(Policy.java:60)
        at com.android.internal.policy.PolicyManager.makeNewWindow(PolicyManager.java:57)
        at android.app.Activity.attach(Activity.java:5932)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2259)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2387) 
        at android.app.ActivityThread.access$800(ActivityThread.java:151) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303) 
        at android.os.Handler.dispatchMessage(Handler.java:102) 
        at android.os.Looper.loop(Looper.java:135) 
        at android.app.ActivityThread.main(ActivityThread.java:5254) 
        at java.lang.reflect.Method.invoke(Native Method) 

Originally posted by @antercepter in #29

Crash with Resources$NotFoundException

App periodically crashes with this stack trace. Steps to reproduce are unknown

Fatal Exception: android.content.res.Resources$NotFoundException
Resource ID #0x0
android.content.res.ResourcesImpl.getValue (ResourcesImpl.java:237)
android.content.res.Resources.loadXmlResourceParser (Resources.java:2293)
android.content.res.Resources.getLayout (Resources.java:1187)
android.view.LayoutInflater.inflate (LayoutInflater.java:532)
io.github.inflationx.viewpump.internal.-ViewPumpLayoutInflater.inflate (-ViewPumpLayoutInflater.java:48)
android.widget.ArrayAdapter.createViewFromResource (ArrayAdapter.java:428)
android.widget.ArrayAdapter.getView (ArrayAdapter.java:419)
OT.getView (OT.java:2)
android.widget.AbsListView.obtainView (AbsListView.java:2472)
android.widget.ListView.measureHeightOfChildren (ListView.java:1421)
android.widget.ListView.onMeasure (ListView.java:1327)
android.view.View.measure (View.java:24777)
android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6858)
android.widget.FrameLayout.onMeasure (FrameLayout.java:194)
android.view.View.measure (View.java:24777)
android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6858)
android.widget.FrameLayout.onMeasure (FrameLayout.java:194)
android.view.View.measure (View.java:24777)
com.android.internal.widget.AlertDialogLayout.tryOnMeasure (AlertDialogLayout.java:146)
com.android.internal.widget.AlertDialogLayout.onMeasure (AlertDialogLayout.java:71)
android.view.View.measure (View.java:24777)
android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6858)
android.widget.FrameLayout.onMeasure (FrameLayout.java:194)
android.view.View.measure (View.java:24777)
android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6858)
android.widget.FrameLayout.onMeasure (FrameLayout.java:194)
android.view.View.measure (View.java:24777)
android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6858)
android.widget.FrameLayout.onMeasure (FrameLayout.java:194)
com.android.internal.policy.DecorView.onMeasure (DecorView.java:749)
android.view.View.measure (View.java:24777)
android.view.ViewRootImpl.performMeasure (ViewRootImpl.java:3282)
android.view.ViewRootImpl.measureHierarchy (ViewRootImpl.java:2025)
android.view.ViewRootImpl.performTraversals (ViewRootImpl.java:2354)
android.view.ViewRootImpl.doTraversal (ViewRootImpl.java:1940)
android.view.ViewRootImpl$TraversalRunnable.run (ViewRootImpl.java:8073)
android.view.Choreographer$CallbackRecord.run (Choreographer.java:1227)
android.view.Choreographer.doCallbacks (Choreographer.java:1029)
android.view.Choreographer.doFrame (Choreographer.java:942)
android.view.Choreographer$FrameHandler.handleMessage (Choreographer.java:1151)
android.os.Handler.dispatchMessage (Handler.java:107)
android.os.Looper.loop (Looper.java:214)
android.app.ActivityThread.main (ActivityThread.java:7707)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:516)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:950)

Philology version 2.0.1

The most crashes on Samsung, Huawei, Xiaomi, 100% crashes on Android 8 or higher

Activity#getTitle method returns untranslated activity title

Describe the bug
Activity#getTitle method returns untranslated label we set in android:label attribute in the AndroidManifest.xml and we cannot intercept it. The workaround is to read label of the activity directly from the AndroidManifest.xml and after that request string by this id from repository:

ActivityInfo activityInfo = getPackageManager().getActivityInfo(
                        getComponentName(),
                        PackageManager.GET_META_DATA
                );
 String label = getString(activityInfo.labelRes);

Expected behavior
Activity#getTitle method should return translated label

Library version
2.0.1

Appcompat version 1.1.0 breaks resource interception

Describe the bug
Updating Appcompat library to 1.1.0 on the app side stop's Philology intercepting the text value on devices with API levels 21-25. Since the problem doesn't reproduce on versions bellow 1.1.0-rc01 most likely it is caused by the 26079d8 commit (diff) with the description:

Turned out to be a gnarly framework bug on API levels 21-25. The test was failing due to the application Resources seemingly being updated when it shouldn't have been. Worked around by always using applyOverrideConfiguration() on those API levels, to force a new Resources instance.

To Reproduce

  1. Update the Appcompat by changing Dependencies#APP_COMPAT_VERSION to 1.1.0 or change implementation Dependencies.appCompat to implementation "androidx.appcompat:appcompat:1.1.0" just for the sample module.
  2. Run the sample app

Expected behavior
Philology should replace the text in the sample app based on the device locale.

Actual behavior
Philology doesn't replace the text in the sample app.

Logs/Screenshots

Library version
2.0.1

Create Git tags for each release

It is a good practice to tie the release artifacts with the Git commit it was created from. Please comsider using Git tags as it is the usual approach.

Unable to download the dependency

Getiing below error when syncing gradle files

Failed to resolve: com.jcminarro:philology:2.1.0

We are not having issues with other dependencies.

Question: can this library help with integration of translation SDKs, such as Lokalise ?

I've noticed some edge cases that their SDK doesn't cover, such as inflation of Views, outside of Activity (example is inflation via the class that extends Application, to show content on top using SAW permission) .

Could this library be used such that whenever the app needs to get an instance of Resources class, it would use the one of the SDK , including when inflating of Views ?

I ask because I've found this article, saying ViewPump might help:

https://jitinsharma.com/posts/taming-android-resources-and-layoutinflater-for-string-manipulation/?q=cache:NtfvHgBjYAgJ:https://jitinsharma.com/posts/taming-android-resources-and-layoutinflater-for-string-manipulation/%20&cd=19&hl=en&ct=clnk&gl=il

And on ViewPump library, it says the current repository could help:
https://github.com/InflationX/ViewPump/wiki/Recipes#update-hardcoded-strings-with-new-text

Crash in webview

Describe the bug
App crash is occured.

To Reproduce
Steps to reproduce the problem:

  1. Launch app using Chrome webview.
  2. Choose Input window and Open EditText with keyboard.
  3. Using Google voice via keyboard's option menu, input any text.
  4. Select text that is input by Google voice.
  5. And Copy or select text.
  6. Repeat steps 4 to 5 1 to 2 times.
  7. App crash is occured.

Expected behavior
No crash

Library version
2.0.1

Logs/Screenshots

Chrome version: 84.0.4147.89 Channel: stable
OS Version: 10.0
Flash Version:

Crash log is following.
Please help to not crash.

07-28 15:46:45.919 11202 11202 W System.err: android.view.InflateException: Binary XML file line #16 in com.google.android.webview:layout/text_edit_suggestion_list_footer: Binary XML file line #16 in com.google.android.webview:layout/text_edit_suggestion_list_footer: Error inflating class TextView
07-28 15:46:45.920 11202 11202 W System.err: Caused by: android.view.InflateException: Binary XML file line #16 in com.google.android.webview:layout/text_edit_suggestion_list_footer: Error inflating class TextView
07-28 15:46:45.920 11202 11202 W System.err: Caused by: android.content.res.Resources$NotFoundException: String resource ID #0x7f100037
07-28 15:46:45.920 11202 11202 W System.err: at android.content.res.Resources.getText(Resources.java:367)
07-28 15:46:45.920 11202 11202 W System.err: at android.widget.TextView.setText(TextView.java:6622)
07-28 15:46:45.920 11202 11202 W System.err: at com.jcminarro.philology.transformer.TextViewTransformer$reword$2$1.invoke(TextViewTransformer.kt:20)
07-28 15:46:45.921 11202 11202 W System.err: at com.jcminarro.philology.transformer.TextViewTransformer$reword$2$1.invoke(TextViewTransformer.kt:8)
07-28 15:46:45.921 11202 11202 W System.err: at com.jcminarro.philology.ViewTransformer$DefaultImpls.setTextIfExists(ViewTransformer.kt:10)
07-28 15:46:45.921 11202 11202 W System.err: at com.jcminarro.philology.transformer.TextViewTransformer.setTextIfExists(TextViewTransformer.kt:8)
07-28 15:46:45.921 11202 11202 W System.err: at com.jcminarro.philology.transformer.TextViewTransformer$reword$2.invoke(TextViewTransformer.kt:20)
07-28 15:46:45.921 11202 11202 W System.err: at com.jcminarro.philology.transformer.TextViewTransformer$reword$2.invoke(TextViewTransformer.kt:8)
07-28 15:46:45.921 11202 11202 W System.err: at com.jcminarro.philology.transformer.AttributeSetExtensionsKt.forEach(AttributeSetExtensions.kt:8)
07-28 15:46:45.921 11202 11202 W System.err: at com.jcminarro.philology.transformer.TextViewTransformer.reword(TextViewTransformer.kt:18)
07-28 15:46:45.921 11202 11202 W System.err: at com.jcminarro.philology.transformer.TextViewTransformer.reword(TextViewTransformer.kt:13)
07-28 15:46:45.921 11202 11202 W System.err: at com.jcminarro.philology.PhilologyInterceptorKt.reword(PhilologyInterceptor.kt:22)
07-28 15:46:45.921 11202 11202 W System.err: at com.jcminarro.philology.PhilologyInterceptorKt.access$reword(PhilologyInterceptor.kt:1)
07-28 15:46:45.921 11202 11202 W System.err: at com.jcminarro.philology.PhilologyInterceptor.rewordView(PhilologyInterceptor.kt:17)
07-28 15:46:45.921 11202 11202 W System.err: at com.jcminarro.philology.PhilologyInterceptor.intercept(PhilologyInterceptor.kt:12)
07-28 15:46:45.921 11202 11202 W System.err: at io.github.inflationx.viewpump.internal.-InterceptorChain.proceed(-InterceptorChain.kt:30)
07-28 15:46:45.921 11202 11202 W System.err: at io.github.inflationx.viewpump.ViewPump.inflate(ViewPump.kt:36)
07-28 15:46:45.922 11202 11202 W System.err: at io.github.inflationx.viewpump.internal.-ViewPumpLayoutInflater$WrapperFactory2.onCreateView(-ViewPumpLayoutInflater.kt:358)
07-28 15:46:45.922 11202 11202 W System.err: at android.view.LayoutInflater.tryCreateView(LayoutInflater.java:1061)
07-28 15:46:45.922 11202 11202 W System.err: at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:997)
07-28 15:46:45.922 11202 11202 W System.err: at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:961)
07-28 15:46:45.922 11202 11202 W System.err: at android.view.LayoutInflater.rInflate(LayoutInflater.java:1123)
07-28 15:46:45.922 11202 11202 W System.err: at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1084)
07-28 15:46:45.922 11202 11202 W System.err: at android.view.LayoutInflater.inflate(LayoutInflater.java:682)
07-28 15:46:45.922 11202 11202 W System.err: at io.github.inflationx.viewpump.internal.-ViewPumpLayoutInflater.inflate(-ViewPumpLayoutInflater.kt:57)
07-28 15:46:45.922 11202 11202 W System.err: at android.view.LayoutInflater.inflate(LayoutInflater.java:534)
07-28 15:46:45.922 11202 11202 W System.err: at io.github.inflationx.viewpump.internal.-ViewPumpLayoutInflater.inflate(-ViewPumpLayoutInflater.kt:48)
07-28 15:46:45.922 11202 11202 W System.err: at android.view.LayoutInflater.inflate(LayoutInflater.java:481)
07-28 15:46:45.922 11202 11202 W System.err: at vL.(chromium-TrichromeWebViewGoogle.aab-stable-1:24)
07-28 15:46:45.922 11202 11202 W System.err: at zL.(chromium-TrichromeWebViewGoogle.aab-stable-1:1)
07-28 15:46:45.922 11202 11202 W System.err: at org.chromium.content.browser.input.TextSuggestionHost.showTextSuggestionMenu(chromium-TrichromeWebViewGoogle.aab-stable-1:5)
07-28 15:46:45.922 11202 11202 W System.err: at android.os.MessageQueue.nativePollOnce(Native Method)
07-28 15:46:45.922 11202 11202 W System.err: at android.os.MessageQueue.next(MessageQueue.java:336)
07-28 15:46:45.923 11202 11202 W System.err: at android.os.Looper.loop(Looper.java:174)
07-28 15:46:45.923 11202 11202 W System.err: at android.app.ActivityThread.main(ActivityThread.java:7576)
07-28 15:46:45.923 11202 11202 W System.err: at java.lang.reflect.Method.invoke(Native Method)
07-28 15:46:45.923 11202 11202 W System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
07-28 15:46:45.923 11202 11202 W System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:937)
07-28 15:46:45.929 11202 11202 F chromium: [FATAL:jni_android.cc(249)] Please include Java exception stack in crash report

getPhilologyRepository function always giving locale as Englies

Describe the bug
I have integrated this library in the Application class as mentioned in the docs but I am facing an issue that always I am getting language as English inside getPhilologyRepository function.

In my app, we set the language based on the user profile after login. For Arabic users, it changes to LTR, and for English users, it changes to RTL layout with respective languages. So user can change language in respective settings and I am setting the default language as per the user. As long as the user is logged in the default language will be the one he has set. Once he logs out it will be English.

I am not sure if this causes since I am setting up the logic in the application class and need your advice.

class SampleApp : Application(), HasAndroidInjector {

    @Inject
    lateinit var androidInjector: DispatchingAndroidInjector<Any>

    override fun onCreate() {
        super.onCreate()
        Cyanea.init(this, resources)
        val calligraphyConfig = CalligraphyConfig.Builder()
            .setDefaultFontPath("fonts/poppins_regular.ttf")
            .setFontAttrId(R.attr.fontPath)
            .build()
        Philology.init(MyPhilologyRepositoryFactory)
        ViewPump.init(
            ViewPump.builder()
                .addInterceptor(CalligraphyInterceptor(calligraphyConfig))
                .addInterceptor(PhilologyInterceptor)
                .build()
        )
        if (BuildConfig.DEBUG) {
            Timber.plant(DebugTree())
        }
    `}`

    override fun attachBaseContext(base: Context) {
        super.attachBaseContext(base)
        if (null == appComponent) {
            appComponent = DaggerAppComponent.builder()
                .appModule(AppModule(this))
                .build()
            appComponent?.inject(this)
        }
    }

    companion object {
        var appComponent: AppComponent? = null
            private set
    }

    override fun androidInjector(): AndroidInjector<Any> {
        return androidInjector
    }
}

object MyPhilologyRepositoryFactory : PhilologyRepositoryFactory {
    override fun getPhilologyRepository(locale: Locale): PhilologyRepository {
        Timber.tag("Philology").d("getPhilologyRepository -> locale - %s", locale.language)
        return when (locale.language) {
            ENGLISH -> EnglishPhilologyRepository
            ARABIC -> ArabicPhilologyRepository
            else -> EnglishPhilologyRepository
        }
    }
}

Bug: can't import&build sample

Describe the bug
Import project from the repository, can't build it and run it

To Reproduce
As I wrote. Clone, build.

Expected behavior
Should be able to build.

Logs/Screenshots
I get this:

image

logs.txt

Library version
2.1.0

Attached project.

Philology.zip

Question of the project?

I am an Android developer from China. What is the use of this project for the application?
I have used the method to set only the String files of different countries. Forgive me for not posting an app on Google Play

Device replacement under system version 8.0 does not work

Hello, I use the tool you provided to implement multi-language. It works well in 8.0 and above, but on the devices below 8.0, the PhilologyRepository I defined cannot get the id of the string, and the getText method of it has no callback Go to call, it didn't work. Is this something I need to set?

Performance

How is the performance of this library? Is it fast? Does it increase screen rendering time?

Strings with <![CDATA[]]> tags

I have some strings with <![CDATA[ strings here ]]> tags, which are used to escape some html tags e.g. <b> </b> or <font> </font>, after adding this library the CDATA tags remain as they were.
In normal getString those CDATA tags were stripped out e.g
<![CDATA[Limit of account <b>500</b> reached]]> is converted to Limit of account <b>500</b> reached but now I get the original string with all those CDATA tags
Any solution for this problem?

Add `Resources#getText(id: Int, def: CharSequence?)` support

Is your feature request related to a problem? Please describe.
When calling Resources#getText(id: Int, def: CharSequence?) Philology will not provide translated value.
At the moment the android.content.res.Resources class has besides the already implemented getString(id: Int) - one more function which receives the provided default value in case when the resource is not found.

Describe the solution you'd like
Add Resources#getText(id: Int, def: CharSequence?) support

one question

Hello, I am using the warehouse you opened as a multi-language switch, but I found that the switch under 7.0 did not work. Setting the interceptor did not get the ID of the string, so I would like to ask What is the lowest Android version that can currently be adapted?

Crash on API < 21

Tested on emulator "Nexus 5X API 19" (Android 4.4 x86)

android.widget.Toolbar has been introduced in API 21, so when using the library with a device with API < 21 , I get the following error

java.lang.NoClassDefFoundError: android.widget.Toolbar
    at com.jcminarro.philology.PhilologyKt$internalViewTransformerFactory$1.getViewTransformer(Philology.kt:61)
    at com.jcminarro.philology.Philology.getViewTransformer$philology_release(Philology.kt:38)
    at com.jcminarro.philology.PhilologyInterceptorKt.reword(PhilologyInterceptor.kt:22)
    at com.jcminarro.philology.PhilologyInterceptorKt.access$reword(PhilologyInterceptor.kt:1)
    at com.jcminarro.philology.PhilologyInterceptor.rewordView(PhilologyInterceptor.kt:17)
    at com.jcminarro.philology.PhilologyInterceptor.intercept(PhilologyInterceptor.kt:12)
    at io.github.inflationx.viewpump.InterceptorChain.proceed(InterceptorChain.java:37)
    at io.github.inflationx.viewpump.ViewPump.inflate(ViewPump.java:49)
    at io.github.inflationx.viewpump.ViewPumpLayoutInflater.onCreateView(ViewPumpLayoutInflater.java:160)
    at android.view.LayoutInflater.onCreateView(LayoutInflater.java:670)
    at io.github.inflationx.viewpump.ViewPumpLayoutInflater.superOnCreateView(ViewPumpLayoutInflater.java:216)
    at io.github.inflationx.viewpump.ViewPumpLayoutInflater.access$100(ViewPumpLayoutInflater.java:14)
    at io.github.inflationx.viewpump.ViewPumpLayoutInflater$ParentAndNameAndAttrsViewCreator.onCreateView(ViewPumpLayoutInflater.java:258)
    at io.github.inflationx.viewpump.FallbackViewCreationInterceptor.intercept(FallbackViewCreationInterceptor.java:11)
    at io.github.inflationx.viewpump.InterceptorChain.proceed(InterceptorChain.java:37)
    at com.jcminarro.philology.PhilologyInterceptor.intercept(PhilologyInterceptor.kt:10)
    at io.github.inflationx.viewpump.InterceptorChain.proceed(InterceptorChain.java:37)
    at io.github.inflationx.viewpump.ViewPump.inflate(ViewPump.java:49)
    at io.github.inflationx.viewpump.ViewPumpLayoutInflater.onCreateView(ViewPumpLayoutInflater.java:144)
    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:695)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:469)
    at io.github.inflationx.viewpump.ViewPumpLayoutInflater.inflate(ViewPumpLayoutInflater.java:55)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
    at android.view.LayoutInflater.inflate(LayoutInflater.java:353)
    at com.android.internal.policy.impl.PhoneWindow.generateLayout(PhoneWindow.java:3022)
    at com.android.internal.policy.impl.PhoneWindow.installDecor(PhoneWindow.java:3085)
    at com.android.internal.policy.impl.PhoneWindow.getDecorView(PhoneWindow.java:1678)
    at android.support.v7.app.AppCompatDelegateImplV9.createSubDecor(AppCompatDelegateImplV9.java:374)
    at android.support.v7.app.AppCompatDelegateImplV9.ensureSubDecor(AppCompatDelegateImplV9.java:323)
    at android.support.v7.app.AppCompatDelegateImplV9.setContentView(AppCompatDelegateImplV9.java:284)
    at android.support.v7.app.AppCompatActivity.setContentView(AppCompatActivity.java:139)

Appcompat version 1.2.0 breaks resource interception

Describe the bug
I think the interception broke again with AppCompat 1.2.0. My libraries versions:

    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.1'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0-alpha02'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
    implementation 'androidx.navigation:navigation-fragment:2.3.0'
    implementation 'androidx.navigation:navigation-ui:2.3.0'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
    implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
    implementation 'androidx.preference:preference-ktx:1.1.1'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    implementation 'androidx.browser:browser:1.2.0'
    implementation 'androidx.webkit:webkit:1.3.0'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'

I tried the configChanges workaround from #23, but it seems to not help this time around.

To Reproduce

  • Update appcompat to 1.2.0
  • Default app strings are used instead

Expected behavior

  • Injection works

Provide a method to download and parsing localization files automatically?

Why not just provide 1 method to handle all of the task like this:

Philology.rewordLocalization(url: "http://....", forLocale: Locale.ENGLISH);
  1. Reword ( if (the downloaded file exist) { reword() } else { notReword() } )
  2. Download ( a .xml file by url )
  3. Save ( into a local file path )
  4. do Reword again

Strings with HTML tags are not working as expected

Describe the bug
We are fetching different language strings from server. We have strings with html tags, those are not getting converted as expected

Ex:
Here is the string from string.xml. :-
<string name="how_it_works_2_subtitle"><b>Earn <font fgcolor="#008900">10</font> 7Rewards points with\nevery dollar you spend on eligible\npurchases + enjoy even more with\n<font fgcolor="#008900">Bonus Point</font> offers!</b></string>

String from server response
“how_it_works_2_subtitle”: “<b>Earn <font fgcolor="#008900">10</font> 7Rewards points with\nevery dollar you spend on eligible\npurchases + enjoy even more with\n<font fgcolor="#008900">Bonus Point</font> offers!</b>”

I am using below function in Philology getText (key: String) method to convert

`   var str = resourceJSONObject.optString(key)
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                     Html.fromHtml(str, Html.FROM_HTML_MODE_COMPACT).toString()
                    } else {
                        Html.fromHtml(str).toString()
                    }`

But styles are not applying to these strings when i use Philology.

Expected behavior
Styles are not applying .String should be in bold format along with font color.
I have tried with encoding HTML tags, that also not worked for me.

Logs/Screenshots
Styles are not applying here
image

Library version
2.0.1

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.