feat: settings option to change the app language

This commit is contained in:
2024-07-12 02:11:36 +02:00
parent 6b64ead713
commit 37b5ca8917
11 changed files with 144 additions and 29 deletions
+5 -2
View File
@@ -9,8 +9,8 @@ plugins {
} }
val keystoreProperties = Properties() val keystoreProperties = Properties()
val keystorePropertiesFile: File? = rootProject.file("keystore.properties") val keystorePropertiesFile: File = rootProject.file("keystore.properties")
if (keystorePropertiesFile?.exists() == true) { if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile)) keystoreProperties.load(FileInputStream(keystorePropertiesFile))
} }
@@ -30,6 +30,9 @@ android {
useSupportLibrary = true useSupportLibrary = true
} }
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
// https://developer.android.com/guide/topics/resources/app-languages#gradle-config
resourceConfigurations.plus(listOf("en", "ar"))
} }
signingConfigs { signingConfigs {
+12 -1
View File
@@ -12,6 +12,7 @@
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:localeConfig="@xml/locales_config"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
@@ -21,7 +22,7 @@
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="screenSize|screenLayout|keyboardHidden|orientation" android:configChanges="screenSize|screenLayout|keyboardHidden|orientation|locale"
android:exported="true" android:exported="true"
android:theme="@style/Theme.LyricsGrabbr.Splash" android:theme="@style/Theme.LyricsGrabbr.Splash"
> >
@@ -45,5 +46,15 @@
android:name="android.app.NOTIFICATION_LISTENER" android:name="android.app.NOTIFICATION_LISTENER"
android:value="Media playback notification listener service" /> android:value="Media playback notification listener service" />
</service> </service>
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false"
>
<meta-data
android:name="autoStoreLocales"
android:value="true"
/>
</service>
</application> </application>
</manifest> </manifest>
@@ -8,6 +8,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
@@ -45,10 +46,9 @@ data class NotificationEvent(
val artist: String, val artist: String,
val title: String, val title: String,
val playbackState: Int, val playbackState: Int,
val duration: Long
) )
class MainActivity : ComponentActivity() { class MainActivity : AppCompatActivity() {
private var lastNotification: MutableStateFlow<NotificationEvent?> = MutableStateFlow(null) private var lastNotification: MutableStateFlow<NotificationEvent?> = MutableStateFlow(null)
private var isReceiverRunning = false private var isReceiverRunning = false
@@ -175,14 +175,12 @@ class MainActivity : ComponentActivity() {
val artist = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_ARTIST) val artist = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_ARTIST)
val songTitle = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_SONG_TITLE) val songTitle = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_SONG_TITLE)
val playbackState = intent.getIntExtra(NotificationListener.NOTIFICATION_PACKAGE_PLAYBACK_STATE, -1) val playbackState = intent.getIntExtra(NotificationListener.NOTIFICATION_PACKAGE_PLAYBACK_STATE, -1)
val duration = intent.getLongExtra(NotificationListener.NOTIFICATION_PACKAGE_DURATION, -1)
val notifPackageName = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_NAME) val notifPackageName = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_NAME)
lastNotification.update { NotificationEvent( lastNotification.update { NotificationEvent(
title = songTitle!!, title = songTitle!!,
artist = artist!!, artist = artist!!,
playbackState = playbackState, playbackState = playbackState,
duration = duration,
packageName = notifPackageName!!, packageName = notifPackageName!!,
) } ) }
} }
@@ -18,6 +18,7 @@ import android.service.notification.StatusBarNotification
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.os.BundleCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
class NotificationListener: NotificationListenerService() { class NotificationListener: NotificationListenerService() {
@@ -113,14 +114,11 @@ class NotificationListener: NotificationListenerService() {
if (ContextCompat.checkSelfPermission(this, POST_NOTIFICATIONS) != PERMISSION_GRANTED) { if (ContextCompat.checkSelfPermission(this, POST_NOTIFICATIONS) != PERMISSION_GRANTED) {
return return
} }
// Apparently the new getParcelable API is buggy and throws an exception if used.
// so we continue to use the deprecated API even on newer versions. val mediaSessionToken = BundleCompat.getParcelable(sbn.notification.extras, Notification.EXTRA_MEDIA_SESSION, MediaSession.Token::class.java)
// https://issuetracker.google.com/issues/240585930
val mediaSessionToken = sbn.notification.extras.getParcelable<MediaSession.Token>(Notification.EXTRA_MEDIA_SESSION)
?: return ?: return
val mediaCtrl = MediaController(context, mediaSessionToken) val mediaCtrl = MediaController(context, mediaSessionToken)
val duration = mediaCtrl.metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.div(1000)
val playbackState = mediaCtrl.playbackState?.state val playbackState = mediaCtrl.playbackState?.state
val albumArt = mediaCtrl.metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART) val albumArt = mediaCtrl.metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
@@ -133,7 +131,6 @@ class NotificationListener: NotificationListenerService() {
intent.putExtra(NOTIFICATION_PACKAGE_NAME, sbn.packageName) intent.putExtra(NOTIFICATION_PACKAGE_NAME, sbn.packageName)
intent.putExtra(NOTIFICATION_PACKAGE_ARTIST, packageArtist) intent.putExtra(NOTIFICATION_PACKAGE_ARTIST, packageArtist)
intent.putExtra(NOTIFICATION_PACKAGE_SONG_TITLE, packageSongTitle) intent.putExtra(NOTIFICATION_PACKAGE_SONG_TITLE, packageSongTitle)
intent.putExtra(NOTIFICATION_PACKAGE_DURATION, duration)
intent.putExtra(NOTIFICATION_PACKAGE_PLAYBACK_STATE, playbackState) intent.putExtra(NOTIFICATION_PACKAGE_PLAYBACK_STATE, playbackState)
if (playbackState == PlaybackState.STATE_PAUSED) { if (playbackState == PlaybackState.STATE_PAUSED) {
@@ -166,7 +163,6 @@ class NotificationListener: NotificationListenerService() {
const val NOTIFICATION_PACKAGE_ARTIST = "package_artist" const val NOTIFICATION_PACKAGE_ARTIST = "package_artist"
const val NOTIFICATION_PACKAGE_SONG_TITLE = "package_song_title" const val NOTIFICATION_PACKAGE_SONG_TITLE = "package_song_title"
const val NOTIFICATION_PACKAGE_PLAYBACK_STATE = "package_playback_state" const val NOTIFICATION_PACKAGE_PLAYBACK_STATE = "package_playback_state"
const val NOTIFICATION_PACKAGE_DURATION = "package_duration"
const val NOTIFICATION_ID = 1 const val NOTIFICATION_ID = 1
const val NOTIFICATION_CHANNEL_ID = "Lyrics" const val NOTIFICATION_CHANNEL_ID = "Lyrics"
const val START_FOREGROUND_SERVICE_ACTION = "START_SERVICE" const val START_FOREGROUND_SERVICE_ACTION = "START_SERVICE"
@@ -30,9 +30,9 @@ import androidx.compose.material.icons.filled.MusicNote
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -48,7 +48,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -86,7 +85,6 @@ import com.illusionman1212.lyricsgrabbr.viewmodels.HomeViewModel
import com.illusionman1212.lyricsgrabbr.viewmodels.SearchResult import com.illusionman1212.lyricsgrabbr.viewmodels.SearchResult
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.illusionman1212.lyricsgrabbr.LGApp
import com.illusionman1212.lyricsgrabbr.ui.components.forwardingPainter import com.illusionman1212.lyricsgrabbr.ui.components.forwardingPainter
import com.illusionman1212.lyricsgrabbr.utils.annotatedStringResource import com.illusionman1212.lyricsgrabbr.utils.annotatedStringResource
import com.illusionman1212.lyricsgrabbr.viewmodels.LyricsViewModel import com.illusionman1212.lyricsgrabbr.viewmodels.LyricsViewModel
@@ -106,7 +104,6 @@ fun HomePage(
lastNotification: MutableStateFlow<NotificationEvent?> lastNotification: MutableStateFlow<NotificationEvent?>
) { ) {
val context = LocalContext.current val context = LocalContext.current
val application = (context as Activity).application as LGApp
val lifecycleOwner = LocalLifecycleOwner.current val lifecycleOwner = LocalLifecycleOwner.current
val lifecycleState by lifecycleOwner.lifecycle.currentStateAsState() val lifecycleState by lifecycleOwner.lifecycle.currentStateAsState()
@@ -124,7 +121,7 @@ fun HomePage(
) } ) }
var shouldShowNotificationPermissionRationale by remember { mutableStateOf( var shouldShowNotificationPermissionRationale by remember { mutableStateOf(
ActivityCompat.shouldShowRequestPermissionRationale( ActivityCompat.shouldShowRequestPermissionRationale(
context, context as Activity,
POST_NOTIFICATIONS POST_NOTIFICATIONS
) )
)} )}
@@ -415,7 +412,7 @@ fun Results(
) )
if (it != results.last()) if (it != results.last())
Divider() HorizontalDivider()
} }
} }
} }
@@ -1,5 +1,6 @@
package com.illusionman1212.lyricsgrabbr.ui.screens package com.illusionman1212.lyricsgrabbr.ui.screens
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -12,10 +13,12 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.outlined.Palette import androidx.compose.material.icons.outlined.Palette
import androidx.compose.material.icons.outlined.Translate
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@@ -32,10 +35,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.illusionman1212.lyricsgrabbr.R import com.illusionman1212.lyricsgrabbr.R
import com.illusionman1212.lyricsgrabbr.ui.components.LGAlertDialog import com.illusionman1212.lyricsgrabbr.ui.components.LGAlertDialog
@@ -43,7 +48,7 @@ import com.illusionman1212.lyricsgrabbr.ui.components.LGRadioButton
import com.illusionman1212.lyricsgrabbr.ui.theme.ColorSecondaryLight import com.illusionman1212.lyricsgrabbr.ui.theme.ColorSecondaryLight
import com.illusionman1212.lyricsgrabbr.data.Theme import com.illusionman1212.lyricsgrabbr.data.Theme
import com.illusionman1212.lyricsgrabbr.ui.components.LGIconButton import com.illusionman1212.lyricsgrabbr.ui.components.LGIconButton
import com.illusionman1212.lyricsgrabbr.utils.mirror import com.illusionman1212.lyricsgrabbr.utils.getLangPreferenceDropdownEntries
import com.illusionman1212.lyricsgrabbr.viewmodels.SettingsState import com.illusionman1212.lyricsgrabbr.viewmodels.SettingsState
import com.illusionman1212.lyricsgrabbr.viewmodels.SettingsViewModel import com.illusionman1212.lyricsgrabbr.viewmodels.SettingsViewModel
@@ -72,8 +77,7 @@ fun SettingsPage(
) { ) {
LGIconButton(tooltip = stringResource(id = R.string.go_back), onClick = goBack) { LGIconButton(tooltip = stringResource(id = R.string.go_back), onClick = goBack) {
Icon( Icon(
modifier = Modifier.mirror(), imageVector = Icons.AutoMirrored.Filled.ArrowBack,
imageVector = Icons.Filled.ArrowBack,
contentDescription = stringResource( contentDescription = stringResource(
id = R.string.go_back id = R.string.go_back
) ) ) )
@@ -97,6 +101,7 @@ fun SettingsPage(
@Composable @Composable
fun Appearance(viewModel: SettingsViewModel, uiState: SettingsState) { fun Appearance(viewModel: SettingsViewModel, uiState: SettingsState) {
var themeOpenDialog by remember { mutableStateOf(false) } var themeOpenDialog by remember { mutableStateOf(false) }
var languageOpenDialog by remember { mutableStateOf(false) }
SettingGroup(title = stringResource(id = R.string.appearance)) { SettingGroup(title = stringResource(id = R.string.appearance)) {
DialogSetting( DialogSetting(
@@ -105,6 +110,17 @@ fun Appearance(viewModel: SettingsViewModel, uiState: SettingsState) {
icon = Icons.Outlined.Palette, icon = Icons.Outlined.Palette,
onClick = { themeOpenDialog = true } onClick = { themeOpenDialog = true }
) )
DialogSetting(
title = stringResource(id = R.string.language),
value = AppCompatDelegate.getApplicationLocales()[0]?.let {
AppCompatDelegate.getApplicationLocales()[0]?.getDisplayLanguage(
it
)
}
?: stringResource(id = R.string._default),
icon = Icons.Outlined.Translate,
onClick = { languageOpenDialog = true }
)
} }
if (themeOpenDialog) { if (themeOpenDialog) {
ThemeDialog( ThemeDialog(
@@ -115,6 +131,11 @@ fun Appearance(viewModel: SettingsViewModel, uiState: SettingsState) {
} }
) )
} }
if (languageOpenDialog) {
LanguageDialog(
onDismiss = { languageOpenDialog = false },
)
}
} }
@Composable @Composable
@@ -159,6 +180,47 @@ fun ThemeDialog(
} }
} }
@Composable
fun LanguageDialog(
onDismiss: () -> Unit,
) {
val langs = LocalContext.current.getLangPreferenceDropdownEntries()
LGAlertDialog(
onDismiss = onDismiss,
title = stringResource(id = R.string.language),
buttons = {
TextButton(onClick = onDismiss) {
Text(stringResource(id = R.string.cancel))
}
},
modifier = Modifier.fillMaxWidth()
) {
LazyColumn {
item {
LGRadioButton(
text = stringResource(id = R.string._default),
onClick = {
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList())
},
selected = AppCompatDelegate.getApplicationLocales().isEmpty,
)
}
for ((key, value) in langs) {
item {
LGRadioButton(
text = key,
onClick = {
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(value))
},
selected = AppCompatDelegate.getApplicationLocales()[0]?.language == value,
)
}
}
}
}
}
@Composable @Composable
fun SettingGroup(title: String, content: @Composable () -> Unit) { fun SettingGroup(title: String, content: @Composable () -> Unit) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
@@ -169,7 +231,7 @@ fun SettingGroup(title: String, content: @Composable () -> Unit) {
} }
@Composable @Composable
fun Header(title: String) { private fun Header(title: String) {
Row(Modifier.padding(start = 56.dp)) { Row(Modifier.padding(start = 56.dp)) {
Text(text = title, color = MaterialTheme.colorScheme.primary) Text(text = title, color = MaterialTheme.colorScheme.primary)
} }
@@ -1,5 +1,6 @@
package com.illusionman1212.lyricsgrabbr.utils package com.illusionman1212.lyricsgrabbr.utils
import android.content.Context
import android.text.Annotation import android.text.Annotation
import android.text.SpannedString import android.text.SpannedString
import androidx.annotation.StringRes import androidx.annotation.StringRes
@@ -7,6 +8,11 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.core.os.LocaleListCompat
import com.illusionman1212.lyricsgrabbr.R
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import java.io.IOException
// Native stylization is coming soon // Native stylization is coming soon
// https://issuetracker.google.com/issues/139320238 // https://issuetracker.google.com/issues/139320238
@@ -32,3 +38,37 @@ fun annotatedStringResource(
} }
return resultBuilder.toAnnotatedString() return resultBuilder.toAnnotatedString()
} }
fun Context.getLocaleListFromXml(): LocaleListCompat {
val tagsList = mutableListOf<CharSequence>()
try {
val xpp: XmlPullParser = resources.getXml(R.xml.locales_config)
while (xpp.eventType != XmlPullParser.END_DOCUMENT) {
if (xpp.eventType == XmlPullParser.START_TAG) {
if (xpp.name == "locale") {
tagsList.add(xpp.getAttributeValue(0))
}
}
xpp.next()
}
} catch (e: XmlPullParserException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
return LocaleListCompat.forLanguageTags(tagsList.joinToString(","))
}
fun Context.getLangPreferenceDropdownEntries(): Map<String, String> {
val localeList = getLocaleListFromXml()
val map = mutableMapOf<String, String>()
for (a in 0 until localeList.size()) {
localeList[a].let {
it?.let { it1 -> map.put(it1.getDisplayName(it), it.toLanguageTag()) }
}
}
return map
}
@@ -1,6 +1,5 @@
package com.illusionman1212.lyricsgrabbr.viewmodels package com.illusionman1212.lyricsgrabbr.viewmodels
import android.content.Context
import android.net.Uri import android.net.Uri
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@@ -85,7 +84,7 @@ data class HomeState(
val error: String? = null, val error: String? = null,
) )
class HomeViewModel(private val context: Context, private val homeRepository: HomeRepository): ViewModel() { class HomeViewModel(private val homeRepository: HomeRepository): ViewModel() {
private val _uiState = MutableStateFlow(HomeState()) private val _uiState = MutableStateFlow(HomeState())
val uiState = _uiState.asStateFlow() val uiState = _uiState.asStateFlow()
private val json = Json { private val json = Json {
@@ -134,7 +133,7 @@ class HomeViewModel(private val context: Context, private val homeRepository: Ho
val Factory: ViewModelProvider.Factory = viewModelFactory { val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer { initializer {
val application = (this[APPLICATION_KEY] as LGApp) val application = (this[APPLICATION_KEY] as LGApp)
HomeViewModel(application.applicationContext, application.homeRepository) HomeViewModel(application.homeRepository)
} }
} }
} }
+2
View File
@@ -32,6 +32,8 @@
<string name="theme_light">نهار</string> <string name="theme_light">نهار</string>
<string name="theme_dark">ليلي</string> <string name="theme_dark">ليلي</string>
<string name="theme_system">استخدم مظهر النظام</string> <string name="theme_system">استخدم مظهر النظام</string>
<string name="language">اللغة</string>
<string name="_default">الإفتراضي</string>
<!-- Lyrics --> <!-- Lyrics -->
<string name="lyrics">كلمات الأغنية</string> <string name="lyrics">كلمات الأغنية</string>
+2
View File
@@ -31,6 +31,8 @@
<string name="theme_light">Light</string> <string name="theme_light">Light</string>
<string name="theme_dark">Dark</string> <string name="theme_dark">Dark</string>
<string name="theme_system">Use System Theme</string> <string name="theme_system">Use System Theme</string>
<string name="language">Language</string>
<string name="_default">Default</string>
<!-- Lyrics --> <!-- Lyrics -->
<string name="lyrics">Lyrics</string> <string name="lyrics">Lyrics</string>
+5
View File
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="en" />
<locale android:name="ar" />
</locale-config>