{"id":15626073,"url":"https://github.com/jcminarro/philology","last_synced_at":"2025-04-05T13:05:48.515Z","repository":{"id":45458624,"uuid":"139248687","full_name":"JcMinarro/Philology","owner":"JcMinarro","description":"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.","archived":false,"fork":false,"pushed_at":"2021-03-26T13:52:48.000Z","size":302,"stargazers_count":511,"open_issues_count":16,"forks_count":39,"subscribers_count":12,"default_branch":"dev","last_synced_at":"2025-03-29T12:05:10.775Z","etag":null,"topics":["android","kotlin","kotlin-library","strings"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JcMinarro.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"jcminarro","ko_fi":"jcminarro"}},"created_at":"2018-06-30T12:44:01.000Z","updated_at":"2025-01-15T15:16:27.000Z","dependencies_parsed_at":"2022-07-14T13:20:58.543Z","dependency_job_id":null,"html_url":"https://github.com/JcMinarro/Philology","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JcMinarro%2FPhilology","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JcMinarro%2FPhilology/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JcMinarro%2FPhilology/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JcMinarro%2FPhilology/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JcMinarro","download_url":"https://codeload.github.com/JcMinarro/Philology/tar.gz/refs/heads/dev","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247339155,"owners_count":20923014,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","kotlin","kotlin-library","strings"],"created_at":"2024-10-03T10:10:01.412Z","updated_at":"2025-04-05T13:05:48.493Z","avatar_url":"https://github.com/JcMinarro.png","language":"Kotlin","funding_links":["https://github.com/sponsors/jcminarro","https://ko-fi.com/jcminarro","https://ko-fi.com/A50744AD"],"categories":[],"sub_categories":[],"readme":"Philology \n=========\n[![CircleCI](https://circleci.com/gh/JcMinarro/Philology/tree/master.svg?style=svg)](https://circleci.com/gh/JcMinarro/Philology/tree/master)  [ ![Download](https://api.bintray.com/packages/jcminarro/maven/Philology/images/download.svg) ](https://bintray.com/jcminarro/maven/Philology/_latestVersion) [![ko-fi](https://www.ko-fi.com/img/donate_sm.png)](https://ko-fi.com/A50744AD)\n\nAn 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.\n\n## Why should I be interested in Philology\n\n### How String resources work on Android?\nAndroid 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.\n\nThat is perfect and I don't want to stop using it.\n\n### The problem\nThese 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.\nThis 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.\n\n### How Philology solves this problem?\n\n_Philology_ doesn't replace the way you are using resources in your current Android Development.\nInstead, 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.\nThis 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.\n\nWith _Philology_ you could replace hardcoded texts instantly and win time before the new release is done.\n\n## Getting Started\n\n### Dependency\nPhilology use [ViewPump](https://github.com/InflationX/ViewPump) library to intercept the view inflate process and reword strings resources. It allows you to use other libraries like [Calligraphy](https://github.com/InflationX/Calligraphy) that intercept the view inflate process\n\n```groovy\ndependencies {\n    compile 'com.jcminarro:Philology:2.1.0'\n    compile 'io.github.inflationx:viewpump:2.0.3'\n}\n```\n\n### Usage\n\n#### Initialize Philology and ViewPump\nDefine your `PhilologyRepositoryFactory` who provides `PhilologyRepository` according with the selected `Locale` on the device.\nKotlin:\n```Kotlin\nclass App : Application() {\n    override fun onCreate() {\n        super.onCreate()\n        // Init Philology with our PhilologyRepositoryFactory\n        Philology.init(MyPhilologyRepositoryFactory)\n        // Add PhilologyInterceptor to ViewPump\n        // If you are already using Calligraphy you can add both interceptors, there is no problem\n        ViewPump.init(ViewPump.builder().addInterceptor(PhilologyInterceptor).build())\n    }\n}\n\nobject MyPhilologyRepositoryFactory : PhilologyRepositoryFactory {\n    override fun getPhilologyRepository(locale: Locale): PhilologyRepository? = when (locale.language) {\n        Locale.ENGLISH.language -\u003e EnglishPhilologyRepository\n        Locale(\"es\", \"ES\").language -\u003e SpanishPhilologyRepository\n        // If we don't support a language we could return null as PhilologyRepository and\n        // values from the strings resources file will be used\n        else -\u003e null\n    }\n}\n\nobject EnglishPhilologyRepository : PhilologyRepository {\n    override fun getText(key: String): CharSequence? = when (key) {\n        \"label\" -\u003e \"New value for the `label` key, it could be fetched from a database or an external API server\"\n        // If we don't want reword a strings we could return null and the value from the string resources file will be used\n        else -\u003e null\n    }\n\n    override fun getPlural(key: String, quantityString: String): CharSequence?  = when (\"${key}_$quantityString\") {\n        \"plurals_label_one\" -\u003e \"New value for the `plurals_label` key and `one` quantity keyword\"\n        \"plurals_label_other\" -\u003e \"New value for the `plurals_label` key and `other` quantity keyword\"\n        // If we don't want reword a plural we could return null and the value from the string resources file will be used\n        else -\u003e null\n    }\n\n   override fun getTextArray(key: String): Array\u003cCharSequence\u003e? = when (key) {\n        \"days\" -\u003e arrayOf(\n            \"Monday\",\n            \"Tuesday\",\n            \"Wednesday\",\n            \"Thursday\",\n            \"Friday\",\n            \"Saturday\",\n            \"Saturday\"\n        )\n        // If we don't want reword a string array we could return null and the value from the string resources file will be used\n        else -\u003e null\n    }\n}\n\nobject SpanishPhilologyRepository : PhilologyRepository { /* Implementation */ }\n```\n\nJava:\n```java\npublic class App extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        PhilologyRepositoryFactory repositoryFactory = new MyPhilologyRepositoryFactory();\n        Philology.INSTANCE.init(repositoryFactory);\n        ViewPump.init(ViewPump.builder().addInterceptor(PhilologyInterceptor.INSTANCE).build());\n    }\n}\n\npublic class MyPhilologyRepositoryFactory extends PhilologyRepositoryFactory {\n    @Nullable\n    @Override\n    public PhilologyRepository getPhilologyRepository(@NotNull Locale locale) {\n        if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) {\n            return new EnglishPhilologyRepository();\n        }\n        return null;\n    }\n}\n\npublic class EnglishPhilologyRepository extends PhilologyRepository {\n    @Nullable\n    @Override\n    public CharSequence getText(@NotNull Resource resource) { /* Implementation */}\n}\n```\n\n#### Inject into Context\nWrap the `Activity` Context.\nKotlin:\n```kotlin\nclass BaseActivity : AppCompatActivity() {\n    override fun attachBaseContext(newBase: Context) {\n        super.attachBaseContext(ViewPumpContextWrapper.wrap(Philology.wrap(newBase)))\n    }\n}\n```\n\nJava:\n```java\npublic class BaseActivity extends AppCompatActivity {\n    @Override\n    protected void attachBaseContext(Context newBase) {\n        super.attachBaseContext(ViewPumpContextWrapper.wrap(Philology.INSTANCE.wrap(newBase)));\n    }\n}\n```\n\n_That is all_\n\n#### CustomViews\nPhilology 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.\n\nKotlin:\n```kotlin\nobject MyCustomViewTransformer : ViewTransformer {\n    override fun reword(view: View, attributeSet: AttributeSet): View = view.apply {\n        when (this) {\n            is MyCustomView -\u003e reword(attributeSet)\n        }\n    }\n\n    private fun MyCustomView.reword(attributeSet: AttributeSet) {\n        @StringRes val textResId = context.getStringResourceId(attributeSet, R.styleable.MyCustomView_text)\n        if (textResId \u003e 0) setTextToMyCustomView(textResId)\n    }\n}\n```\n\nJava:\n```java\npublic class MyCustomViewTransformer extends ViewTransformer {\n    private static String MY_CUSTOM_ATTRIBUTE_NAME = \"text\";\n    @NotNull\n    @Override\n    public View reword(@NotNull View view, @NotNull AttributeSet attributeSet) {\n        if (view instanceof MyCustomView) {\n            MyCustomView myCustomView = (MyCustomView) view;\n            int textResId = getStringResourceId(myCustomView.getContext(), attributeSet, R.styleable.MyCustomView_text);\n            if (textResId \u003e 0) {\n                myCustomView.setTextToMyCustomView(textResId);\n            }\n        }\n        return view;\n    }\n}\n```\n\nAfter you implement your `ViewTransformer` you need to provide it to `Philology` injecting a `ViewTransformerFactory` by the `init()` method\nKotlin:\n```kotlin\nclass App : Application() {\n    override fun onCreate() {\n        super.onCreate()\n        Philology.init(MyPhilologyRepositoryFactory, MyViewTransformerFactory)\n        ViewPump.init(ViewPump.builder().addInterceptor(PhilologyInterceptor.INSTANCE).build());\n    }\n}\n\nobject MyViewTransformerFactory : ViewTransformerFactory{\n    override fun getViewTransformer(view: View): ViewTransformer = when (view) {\n        is MyCustomView -\u003e MyCustomViewTransformer\n        else -\u003e null\n    }\n}\n```\n\nJava:\n```java\n\npublic class App extends Application {\n    @Override\n    public void onCreate() {\n        super.onCreate();\n        PhilologyRepositoryFactory repositoryFactory = new MyPhilologyRepositoryFactory();\n        ViewTransformerFactory viewTransformerFactory = new MyCustomViewTransformerFactory();\n        Philology.INSTANCE.init(repositoryFactory, viewTransformerFactory);\n        ViewPump.init(ViewPump.builder().addInterceptor(PhilologyInterceptor.INSTANCE).build());\n    }\n}\n\npublic class MyCustomViewTransformerFactory implements ViewTransformerFactory {\n    @Nullable\n    @Override\n    public ViewTransformer getViewTransformer(@NotNull View view) {\n        if (view instanceof MyCustomView) {\n            return new MyViewTransformer();\n        }\n        return null;\n    }\n}\n```\n\n## Do you want to contribute?\nFeel free to add any useful feature to the library, we will be glad to improve it with your help.\nI'd love to hear about your use case too, especially if it's not covered perfectly.\n\nDeveloped By\n------------\n\n* Jc Miñarro  - \u003cjosecarlos.minarro@gmail.com\u003e\n\n\u003ca href=\"https://twitter.com/el_joker333\"\u003e\n  \u003cimg alt=\"Follow me on Twitter\" src=\"https://image.freepik.com/iconos-gratis/twitter-logo_318-40209.jpg\" height=\"60\" width=\"60\"/\u003e\n\u003c/a\u003e\n\u003ca href=\"https://www.linkedin.com/in/josecarlosminarrogil/\"\u003e\n  \u003cimg alt=\"Add me to Linkedin\" src=\"https://image.freepik.com/iconos-gratis/boton-del-logotipo-linkedin_318-84979.png\" height=\"60\" width=\"60\"/\u003e\n\u003c/a\u003e\n\u003ca href=\"https://medium.com/@jcminarro\"\u003e\n  \u003cimg alt=\"Follow me on Twitter\" src=\"https://cdn-images-1.medium.com/max/1600/1*emiGsBgJu2KHWyjluhKXQw.png\" height=\"60\" width=\"60\"/\u003e\n\u003c/a\u003e\n\nLicense\n-------\n\n    Copyright 2018 Jc Miñarro\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcminarro%2Fphilology","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjcminarro%2Fphilology","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjcminarro%2Fphilology/lists"}