diff --git a/README.md b/README.md
index 11118d06..13fd0f07 100644
--- a/README.md
+++ b/README.md
@@ -70,14 +70,14 @@ This simple app helps you avoid forgetting to consume foods that are about to ex
![English default](https://img.shields.io/badge/English-default-blue?style=flat-square)
-![Arabic 81%](https://img.shields.io/badge/Arabic-81%25-yellow?style=flat-square)
-![French 96%](https://img.shields.io/badge/French-96%25-green?style=flat-square)
-![German 93%](https://img.shields.io/badge/German-93%25-yellowgreen?style=flat-square)
-![Hindi 70%](https://img.shields.io/badge/Hindi-70%25-orange?style=flat-square)
-![Indonesian 100%](https://img.shields.io/badge/Indonesian-100%25-brightgreen?style=flat-square)
-![Italian 100%](https://img.shields.io/badge/Italian-100%25-brightgreen?style=flat-square)
-![Japanese 84%](https://img.shields.io/badge/Japanese-84%25-yellow?style=flat-square)
-![Spanish 100%](https://img.shields.io/badge/Spanish-100%25-brightgreen?style=flat-square)
+![Arabic 81%](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fraw.githubusercontent.com%2Florenzovngl%2FFoodExpirationDates%2Fmain%2Fshields%2Ftranslations%2Far.json)
+![French 96%](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fraw.githubusercontent.com%2Florenzovngl%2FFoodExpirationDates%2Fmain%2Fshields%2Ftranslations%2Ffr.json)
+![German 93%](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fraw.githubusercontent.com%2Florenzovngl%2FFoodExpirationDates%2Fmain%2Fshields%2Ftranslations%2Fde.json)
+![Hindi 70%](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fraw.githubusercontent.com%2Florenzovngl%2FFoodExpirationDates%2Fmain%2Fshields%2Ftranslations%2Fhi.json)
+![Indonesian 100%](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fraw.githubusercontent.com%2Florenzovngl%2FFoodExpirationDates%2Fmain%2Fshields%2Ftranslations%2Fin.json)
+![Italian 100%](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fraw.githubusercontent.com%2Florenzovngl%2FFoodExpirationDates%2Fmain%2Fshields%2Ftranslations%2Fit.json)
+![Japanese 84%](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fraw.githubusercontent.com%2Florenzovngl%2FFoodExpirationDates%2Fmain%2Fshields%2Ftranslations%2Fja.json)
+![Spanish 100%](https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fraw.githubusercontent.com%2Florenzovngl%2FFoodExpirationDates%2Fmain%2Fshields%2Ftranslations%2Fes.json)
diff --git a/build.gradle.kts b/build.gradle.kts
index ec7c7595..616171d5 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -15,6 +15,7 @@ plugins {
alias(libs.plugins.com.android.application) apply false
alias(libs.plugins.com.android.library) apply false
alias(libs.plugins.org.jetbrains.kotlin.android) apply false
+ alias(libs.plugins.org.jetbrains.kotlin.jvm) apply false
alias(libs.plugins.com.google.dagger.hilt.android) apply false
alias(libs.plugins.app.cash.paparazzi) apply false
alias(libs.plugins.com.google.devtools.ksp) apply false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 28dd1a88..dd1133a4 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -25,6 +25,7 @@ ui-test-junit4 = "1.5.3"
uiautomator = "2.2.0"
work-runtime-ktx = "2.8.1"
splashscreen = "1.0.1"
+org-jetbrains-kotlin-jvm = "1.9.10"
[libraries]
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist-systemuicontroller" }
@@ -75,5 +76,6 @@ com-android-library = {id = "com.android.library", version.ref = "agp"}
com-google-dagger-hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "dagger-hilt"}
com-google-devtools-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp"}
org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "org-jetbrains-kotlin-android" }
+org-jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "org-jetbrains-kotlin-jvm" }
[bundles]
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 9f91450e..d6fd9676 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -14,3 +14,4 @@ dependencyResolutionManagement {
}
rootProject.name = "FoodExpirationDates"
include ':app'
+include ':translation-analyzer'
diff --git a/shields/translations/ar.json b/shields/translations/ar.json
index 33ecda5b..938a5a9f 100644
--- a/shields/translations/ar.json
+++ b/shields/translations/ar.json
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"label": "Arabic",
- "message": "50%",
- "color": "orange",
+ "message": "81%",
+ "color": "yellow",
"style": "flat-square"
}
\ No newline at end of file
diff --git a/shields/translations/fr.json b/shields/translations/fr.json
new file mode 100644
index 00000000..6914c3b2
--- /dev/null
+++ b/shields/translations/fr.json
@@ -0,0 +1,7 @@
+{
+ "schemaVersion": 1,
+ "label": "French",
+ "message": "96%",
+ "color": "green",
+ "style": "flat-square"
+}
\ No newline at end of file
diff --git a/shields/translations/hi.json b/shields/translations/hi.json
index ff4cc328..e2335c1b 100644
--- a/shields/translations/hi.json
+++ b/shields/translations/hi.json
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"label": "Hindi",
- "message": "100%",
- "color": "brightgreen",
+ "message": "70%",
+ "color": "orange",
"style": "flat-square"
}
\ No newline at end of file
diff --git a/shields/translations/in.json b/shields/translations/in.json
new file mode 100644
index 00000000..d12912d4
--- /dev/null
+++ b/shields/translations/in.json
@@ -0,0 +1,7 @@
+{
+ "schemaVersion": 1,
+ "label": "Indonesian",
+ "message": "100%",
+ "color": "brightgreen",
+ "style": "flat-square"
+}
\ No newline at end of file
diff --git a/shields/translations/ja.json b/shields/translations/ja.json
new file mode 100644
index 00000000..eeea0526
--- /dev/null
+++ b/shields/translations/ja.json
@@ -0,0 +1,7 @@
+{
+ "schemaVersion": 1,
+ "label": "Japanese",
+ "message": "84%",
+ "color": "yellow",
+ "style": "flat-square"
+}
\ No newline at end of file
diff --git a/translation-analyzer/.gitignore b/translation-analyzer/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/translation-analyzer/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/translation-analyzer/build.gradle.kts b/translation-analyzer/build.gradle.kts
new file mode 100644
index 00000000..0c3e34da
--- /dev/null
+++ b/translation-analyzer/build.gradle.kts
@@ -0,0 +1,134 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
+plugins {
+ alias(libs.plugins.org.jetbrains.kotlin.jvm)
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+}
+
+dependencies {
+ implementation(libs.kotlin.stdlib)
+}
+
+val compileKotlin: KotlinCompile by tasks
+compileKotlin.kotlinOptions {
+ jvmTarget = "17"
+}
+
+val compileTestKotlin: KotlinCompile by tasks
+compileTestKotlin.kotlinOptions {
+ jvmTarget = "17"
+}
+
+val locales = mapOf(
+ "ar" to "Arabic",
+ "de" to "German",
+ "en" to "English",
+ "es" to "Spanish",
+ "fr" to "French",
+ "hi" to "Hindi",
+ "in" to "Indonesian",
+ "it" to "Italian",
+ "ja" to "Japanese",
+)
+
+var results: List> = mutableListOf()
+
+data class JsonEntry(
+ val schemaVersion: Int = 1,
+ val label: String,
+ val message: String,
+ val color: String,
+ val style: String = "flat-square"
+) {
+ fun toJson() = "{\n" +
+ " \"schemaVersion\": $schemaVersion,\n" +
+ " \"label\": \"$label\",\n" +
+ " \"message\": \"$message\",\n" +
+ " \"color\": \"$color\",\n" +
+ " \"style\": \"$style\"\n" +
+ "}"
+}
+
+fun path(vararg elements: String) = elements.joinToString(separator = File.separator)
+
+fun analyzeTranslations(androidProjectPath: String) {
+ val androidProject = File(androidProjectPath)
+ val defaultXML = androidProject.walkTopDown()
+ .filter {
+ it.absolutePath.endsWith(path("values", "strings.xml")) &&
+ it.absolutePath.contains(path("app", "src", "main"))
+ }.first()
+ androidProject.walkTopDown()
+ .filter {
+ it.name.endsWith("strings.xml") && it.absolutePath.contains("main")
+ }.forEach {
+ parseXML(defaultXML, it)
+ }
+}
+
+fun parseXML(default: File, translation: File) {
+ val folderName = translation.absolutePath.substring(
+ translation.absolutePath.indexOf("values"),
+ translation.absolutePath.lastIndexOf(File.separator)
+ )
+ val localeKey = folderName.let {
+ it.substring(startIndex = it.indexOf("-") + 1)
+ }
+ val locale = locales[localeKey]
+ val percent = (countStrings(translation).toDouble() / countStrings(default) * 100).toInt()
+ val color = percent.let {
+ when {
+ it < 50 -> "red"
+ it < 80 -> "orange"
+ it < 90 -> "yellow"
+ it < 95 -> "yellowgreen"
+ it < 100 -> "green"
+ else -> "brightgreen"
+ }
+ }
+ if (locale != null) {
+ results = results.plus(locale to percent)
+ File(path("shields", "translations", "${localeKey}.json"))
+ .writeText(
+ JsonEntry(
+ label = locale,
+ message = "$percent%",
+ color = color
+ ).toJson()
+ )
+ }
+}
+
+fun countStrings(file: File): Int {
+ val doc = javax.xml.parsers.DocumentBuilderFactory.newInstance()
+ .newDocumentBuilder()
+ .parse(file)
+ doc.documentElement.normalize()
+ return doc.getElementsByTagName("string").length +
+ doc.getElementsByTagName("item").length
+}
+
+tasks.register("analyzeTranslations") {
+ doLast {
+ println("Computing translation statistics...")
+ analyzeTranslations(System.getProperty("user.dir"))
+ println(
+ "Translation statistics: ${
+ results.sortedBy {
+ it.first
+ }.joinToString(separator = ", ") {
+ "${it.first}: ${it.second}%"
+ }
+ }"
+ )
+ }
+}
+
+tasks.named("build") { finalizedBy("analyzeTranslations") }
+
+tasks.named("assemble") { finalizedBy("analyzeTranslations") }
\ No newline at end of file