Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

175 changes: 175 additions & 0 deletions LOCALIZATION_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Multi-Language Localization Setup

This project now supports multiple languages: English, Portuguese, and Spanish.

## 📁 File Structure

```
app/src/main/res/
├── values/
│ └── strings.xml # English strings (default)
├── values-pt/
│ └── strings.xml # Portuguese strings
└── values-es/
└── strings.xml # Spanish strings
```

## 🌐 How It Works

### Automatic Language Detection
- The app automatically detects the device's language setting
- If the device is set to Portuguese, it will use `values-pt/strings.xml`
- If the device is set to Spanish, it will use `values-es/strings.xml`
- If the device is set to any other language, it will use `values/strings.xml` (English)

### String Resources
All user-facing text in the app is now localized:
- **UI Labels**: Buttons, titles, descriptions
- **Dialog Messages**: Confirmations, errors, information
- **Content Descriptions**: Accessibility labels for screen readers
- **Settings**: Configuration options and descriptions

## 🔧 Implementation Details

### Compose UI
The app uses `stringResource()` calls throughout the Compose UI:
```kotlin
Text(stringResource(R.string.app_name))
Text(stringResource(R.string.settings))
```

### Android Manifest
The app name is localized:
```xml
android:label="@string/app_name"
```

## 📝 Adding New Strings

### 1. Add to English (values/strings.xml)
```xml
<string name="new_feature_title">New Feature</string>
```

### 2. Add to Portuguese (values-pt/strings.xml)
```xml
<string name="new_feature_title">Nova Funcionalidade</string>
```

### 3. Add to Spanish (values-es/strings.xml)
```xml
<string name="new_feature_title">Nueva Funcionalidad</string>
```

### 4. Use in Code
```kotlin
Text(stringResource(R.string.new_feature_title))
```

## 🌍 Supported Languages

| Language | Code | File | Status |
|----------|------|------|---------|
| English | `en` | `values/strings.xml` | ✅ Complete |
| Portuguese | `pt` | `values-pt/strings.xml` | ✅ Complete |
| Spanish | `es` | `values-es/strings.xml` | ✅ Complete |

## 🚀 Adding More Languages

To add another language (e.g., French):

1. Create `app/src/main/res/values-fr/strings.xml`
2. Copy all strings from `values/strings.xml`
3. Translate each string to French
4. The app will automatically support French when the device language is set to French

## 📱 Testing Localization

### Method 1: Device Settings
1. Go to Device Settings → System → Languages & input → Languages
2. Add your desired language and move it to the top
3. Restart the app
4. All text should now appear in the selected language

### Method 2: Android Studio
1. Open Android Studio
2. Go to Run → Edit Configurations
3. In "Launch Options", set "Language" to your desired language code (e.g., "es" for Spanish)
4. Run the app - it will launch in the selected language

### Method 3: ADB Command
```bash
# For Spanish
adb shell setprop persist.sys.language es
adb shell setprop persist.sys.country ES

# For Portuguese
adb shell setprop persist.sys.language pt
adb shell setprop persist.sys.country BR

# For English
adb shell setprop persist.sys.language en
adb shell setprop persist.sys.country US
```

## 🔍 Common Issues

### Missing Translations
If a string is missing from a language file:
- The app will fall back to English
- Check the logcat for warnings about missing resources

### String Format Issues
Some strings might need special handling for different languages:
- Date formats
- Number formats
- Pluralization rules
- Gender-specific language variations

### RTL Support
All supported languages (English, Portuguese, Spanish) are left-to-right (LTR), so no special RTL handling is needed.

## 📚 Best Practices

1. **Never hardcode strings** - Always use `stringResource()`
2. **Keep translations up to date** - Update all language files when adding new features
3. **Test on real devices** - Emulator language switching can be unreliable
4. **Use descriptive names** - String keys should be self-explanatory
5. **Group related strings** - Use comments to organize strings by feature
6. **Consider cultural differences** - Some phrases may need adaptation beyond direct translation

## 🛠️ Maintenance

### Regular Tasks
- Review new strings added to English
- Translate new strings to Portuguese and Spanish
- Test all languages regularly
- Update this documentation when adding new languages

### Quality Assurance
- Have native speakers review translations
- Test on different Android versions
- Verify accessibility with screen readers
- Check for text overflow in different languages
- Consider regional variations (e.g., European vs Latin American Spanish)

## 🌎 Regional Considerations

### Spanish
- **Spain (es-ES)**: Uses "vosotros" form and European Spanish vocabulary
- **Latin America (es-419)**: Uses "ustedes" form and Latin American vocabulary
- Current implementation uses neutral Latin American Spanish

### Portuguese
- **Portugal (pt-PT)**: European Portuguese vocabulary and pronunciation
- **Brazil (pt-BR)**: Brazilian Portuguese vocabulary and pronunciation
- Current implementation uses Brazilian Portuguese

## 📖 Resources

- [Android Localization Guide](https://developer.android.com/guide/topics/resources/localization)
- [Android String Resources](https://developer.android.com/guide/topics/resources/string-resource)
- [Compose Localization](https://developer.android.com/jetpack/compose/localization)
- [Material Design Localization](https://material.io/design/usability/bidirectionality.html)
- [Spanish Language Resources](https://www.rae.es/)
- [Portuguese Language Resources](https://www.priberam.pt/)
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ package com.mobyle.abbay.data.datasource.local.books

import com.mobyle.abbay.data.model.BookFileEntity
import com.mobyle.abbay.data.model.MultipleBooksEntity
import com.model.Book
import kotlinx.coroutines.flow.Flow

interface BooksLocalDataSource {
fun observeMultipleBooksList(): Flow<List<MultipleBooksEntity>>

fun observeBookFilesList(): Flow<List<BookFileEntity>>

suspend fun getBookFilesList(): List<BookFileEntity>
suspend fun getBooksList(): List<Book>

suspend fun addBookFileList(filesList: List<BookFileEntity>)

suspend fun getMultipleBooksList(): List<MultipleBooksEntity>

suspend fun addMultipleBooksList(booksList: List<MultipleBooksEntity>)
suspend fun upsertBooksList(
singleFileBooksList: List<BookFileEntity>,
multipleBooksList: List<MultipleBooksEntity>
)

suspend fun deleteBook(id: String)

Expand All @@ -31,7 +31,7 @@ interface BooksLocalDataSource {

fun getBooksFolder(): String?

fun hasShownReloadGuide() : Boolean
fun hasShownReloadGuide(): Boolean

fun setReloadGuideAsShown()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.mobyle.abbay.data.datasource.local.keystore.KeyValueStore
import com.mobyle.abbay.data.datasource.local.keystore.KeyValueStoreKeys
import com.mobyle.abbay.data.model.BookFileEntity
import com.mobyle.abbay.data.model.MultipleBooksEntity
import com.model.Book
import javax.inject.Inject

class BooksLocalDataSourceImpl @Inject constructor(
Expand All @@ -16,16 +17,17 @@ class BooksLocalDataSourceImpl @Inject constructor(

override fun observeBookFilesList() = booksDao.observeBookFilesList()

override suspend fun getBookFilesList(): List<BookFileEntity> = booksDao.getBookFilesList()

override suspend fun addBookFileList(filesList: List<BookFileEntity>) =
booksDao.insertBookFilesList(filesList)

override suspend fun getMultipleBooksList(): List<MultipleBooksEntity> =
booksDao.getMultipleBooksList()
override suspend fun upsertBooksList(
singleFileBooksList: List<BookFileEntity>,
multipleBooksList: List<MultipleBooksEntity>
) {
booksDao.upsertBooksList(
multipleBooksList = multipleBooksList,
singleFileBooksList = singleFileBooksList
)
}

override suspend fun addMultipleBooksList(booksList: List<MultipleBooksEntity>) =
booksDao.insertMultipleBooksList(booksList)
override suspend fun getBooksList(): List<Book> = booksDao.getBooksList()

override suspend fun deleteBook(id: String) {
booksDao.deleteBookFile(id)
Expand All @@ -36,8 +38,7 @@ class BooksLocalDataSourceImpl @Inject constructor(
}

override suspend fun clearBooks() {
booksDao.deleteAllBookFiles()
booksDao.deleteAllMultipleBooks()
booksDao.deleteAllBooks()
keyValueStore.deleteAllBookInformation()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import com.mobyle.abbay.data.mappers.toDomain
import com.mobyle.abbay.data.model.BookFileEntity
import com.mobyle.abbay.data.model.MultipleBooksEntity
import com.model.Book
import kotlinx.coroutines.flow.Flow

@Dao
Expand Down Expand Up @@ -45,4 +48,30 @@ interface BooksDao {

@Query("UPDATE MultipleBooksEntity SET progress = :progress, currentBookPosition = :currentPosition WHERE id = :id")
suspend fun updateMultipleBookProgress(id: String, currentPosition: Int, progress: Long)

@Transaction
suspend fun deleteAllBooks(
) {
deleteAllBookFiles()
deleteAllMultipleBooks()
}

@Transaction
suspend fun upsertBooksList(
multipleBooksList: List<MultipleBooksEntity>,
singleFileBooksList: List<BookFileEntity>
) {
deleteAllBooks()
insertMultipleBooksList(multipleBooksList)
insertBookFilesList(singleFileBooksList)
}

@Transaction
suspend fun getBooksList(): List<Book> {
return getBookFilesList().map {
it.toDomain()
} + getMultipleBooksList().map {
it.toDomain()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.mobyle.abbay.data.mappers

import com.mobyle.abbay.data.model.BookFileEntity
import com.mobyle.abbay.data.model.BookTypeEntity
import com.mobyle.abbay.data.model.MultipleBooksEntity
import com.model.BookFile
import com.model.BookType
import com.model.MultipleBooks
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
Expand All @@ -20,7 +22,8 @@ fun MultipleBooks.toEntity(): MultipleBooksEntity = MultipleBooksEntity(
duration = duration,
currentBookPosition = currentBookPosition,
speed = speed,
hasError = hasError
hasError = hasError,
type = type.toEntity()
)

fun BookFile.toEntity() = BookFileEntity(
Expand All @@ -31,7 +34,13 @@ fun BookFile.toEntity() = BookFileEntity(
progress = progress,
duration = duration,
speed = speed,
hasError = hasError
hasError = hasError,
type = type.toEntity()
)

private fun BookType.toEntity(): BookTypeEntity = when (this) {
BookType.FILE -> BookTypeEntity.FILE
else -> BookTypeEntity.FOLDER
}

private fun BookFileEntity.toJson() = Json.encodeToString(this)
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.mobyle.abbay.data.mappers

import com.mobyle.abbay.data.model.BookFileEntity
import com.mobyle.abbay.data.model.BookTypeEntity
import com.mobyle.abbay.data.model.MultipleBooksEntity
import com.model.BookFile
import com.model.BookType
import com.model.MultipleBooks
import kotlinx.serialization.json.Json

Expand All @@ -18,7 +20,8 @@ fun MultipleBooksEntity.toDomain(): MultipleBooks = MultipleBooks(
duration = duration,
currentBookPosition = currentBookPosition,
speed = speed,
hasError = hasError
hasError = hasError,
type = type.toDomain(),
)

fun BookFileEntity.toDomain() = BookFile(
Expand All @@ -29,7 +32,13 @@ fun BookFileEntity.toDomain() = BookFile(
progress = progress,
duration = duration,
speed = speed,
hasError = hasError
hasError = hasError,
type = type.toDomain(),
)

private fun BookTypeEntity.toDomain(): BookType = when(this) {
BookTypeEntity.FILE -> BookType.FILE
else -> BookType.FOLDER
}

private fun String.toEntity() = Json.decodeFromString<BookFileEntity>(this)
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ class BookFileEntity(
val progress: Long,
val duration: Long,
val speed: Float,
val hasError: Boolean
val hasError: Boolean,
val type: BookTypeEntity
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.mobyle.abbay.data.model

enum class BookTypeEntity {
FOLDER,
FILE
}
Loading