Philology


Source link: https://github.com/JcMinarro/Philology

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:1.0.0'
  compile 'io.github.inflationx:viewpump:1.0.0' 
}

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.ENGLISH.language  == locale.language -> EnglishPhilologyRepository

Locale("es", "ES").language == locale.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 an strings 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 String key) {
 /* 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 {

  private const val MY_CUSTOM_ATTRIBUTE_NAME = "text"
  override fun reword(view: View, attributeSet: AttributeSet): View = view.apply {

when (this) {

 is MyCustomView -> reword(attributeSet)

}

  
}

private fun MyCustomView.reword(attributeSet: AttributeSet) {

attributeSet.forEach {

 when (attributeSet.getAttributeName(it)) {

  TEXT -> setTextIfExists(attributeSet, it, this::setTextToMyCustomView)

 
}

}

  
}
 
}

Java:

public class MyCustomViewTransformer extends ViewTransformer {

  private static String MY_CUSTOM_ATTRIBUTE_NAME = "text";
  @NotNull
  @Override
  public View reword(@NotNull final View view, @NotNull AttributeSet attributeSet) {

if (view instanceof MyCustomView) {

 MyCustomView myCustomView = (MyCustomView) view;

 for (int index=0; index<attributeSet.getAttributeCount();
 index++) {

  if (MY_CUSTOM_ATTRIBUTE_NAME.equals(attributeSet.getAttributeName(index))) {

int resourceValue = attributeSet.getAttributeResourceValue(index, -1);

if (resourceValue != -1) {

 myCustomView.setTextToMyCustomView(resourceValue))

}

  
}

 
}

}

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. 

Resources

A Pin view widget for Android.

PinView has a feature that allows you to find out when they have completed all parameters. Support for Android 3.0 and up. It supports portrait and landscape mode, saving the state.

A ToggleLayout that can be used in setting interface.

An opencore amr codec JNI wrapper with explanation and one solution for packaging amr audio files.

JConditions is an extension for JUnit framework, which allows to mark test methods with specific conditional annotations. It helps to keep clean your test methods and prevents a lot of unnecessary code.

Easy way to handle shared preferences.

A wrapper for sending/receiving messages and data changes between Android Wear and Android Mobile.

Topics


2D Engines   3D Engines   9-Patch   Action Bars   Activities   ADB   Advertisements   Analytics   Animations   ANR   AOP   API   APK   APT   Architecture   Audio   Autocomplete   Background Processing   Backward Compatibility   Badges   Bar Codes   Benchmarking   Bitmaps   Bluetooth   Blur Effects   Bread Crumbs   BRMS   Browser Extensions   Build Systems   Bundles   Buttons   Caching   Camera   Canvas   Cards   Carousels   Changelog   Checkboxes   Cloud Storages   Color Analysis   Color Pickers   Colors   Comet/Push   Compass Sensors   Conferences   Content Providers   Continuous Integration   Crash Reports   Credit Cards   Credits   CSV   Curl/Flip   Data Binding   Data Generators   Data Structures   Database   Database Browsers   Date &   Debugging   Decompilers   Deep Links   Dependency Injections   Design   Design Patterns   Dex   Dialogs   Distributed Computing   Distribution Platforms   Download Managers   Drawables   Emoji   Emulators   EPUB   Equalizers &   Event Buses   Exception Handling   Face Recognition   Feedback &   File System   File/Directory   Fingerprint   Floating Action   Fonts   Forms   Fragments   FRP   FSM   Functional Programming   Gamepads   Games   Geocaching   Gestures   GIF   Glow Pad   Gradle Plugins   Graphics   Grid Views   Highlighting   HTML   HTTP Mocking   Icons   IDE   IDE Plugins   Image Croppers   Image Loaders   Image Pickers   Image Processing   Image Views   Instrumentation   Intents   Job Schedulers   JSON   Keyboard   Kotlin   Layouts   Library Demos   List View   List Views   Localization   Location   Lock Patterns   Logcat   Logging   Mails   Maps   Markdown   Mathematics   Maven Plugins   MBaaS   Media   Menus   Messaging   MIME   Mobile Web   Native Image   Navigation   NDK   Networking   NFC   NoSQL   Number Pickers   OAuth   Object Mocking   OCR Engines   OpenGL   ORM   Other Pickers   Parallax List   Parcelables   Particle Systems   Password Inputs   PDF   Permissions   Physics Engines   Platforms   Plugin Frameworks   Preferences   Progress Indicators   ProGuard   Properties   Protocol Buffer   Pull To   Purchases   Push/Pull   QR Codes   Quick Return   Radio Buttons   Range Bars   Ratings   Recycler Views   Resources   REST   Ripple Effects   RSS   Screenshots   Scripting   Scroll Views   SDK   Search Inputs   Security   Sensors   Services   Showcase Views   Signatures   Sliding Panels   Snackbars   SOAP   Social Networks   Spannable   Spinners   Splash Screens   SSH   Static Analysis   Status Bars   Styling   SVG   System   Tags   Task Managers   TDD &   Template Engines   Testing   Testing Tools   Text Formatting   Text Views   Text Watchers   Text-to   Toasts   Toolkits For   Tools   Tooltips   Trainings   TV   Twitter   Updaters   USB   User Stories   Utils   Validation   Video   View Adapters   View Pagers   Views   Watch Face   Wearable Data   Wearables   Weather   Web Tools   Web Views   WebRTC   WebSockets   Wheel Widgets   Wi-Fi   Widgets   Windows   Wizards   XML   XMPP   YAML   ZIP Codes