Skip to content
Open
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
36 changes: 36 additions & 0 deletions app/localization_plan.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
Localization Plan

1) Inventory and extraction
- Identify all user-visible strings across UI: labels, placeholders, button texts, dialogs, toasts, headings, contentDescription for icons and images.
- Replace hardcoded literals with references to resources in res/values/strings.xml.

2) Resource structuring
- Use clear, stable keys (prefix with context where helpful: screen_, action_, content_desc_).
- Add <plurals> for count-dependent text and <string-array> for option lists where needed.
- Mark non-translatable items with translatable="false" (e.g., symbols like "/").

3) Compose usage
- Use stringResource(R.string.key) in composables.
- For non-composable helpers, pass localized strings in as parameters or compute at call sites.

4) Locale-specific resources
- Add res/values-<lang>/strings.xml (e.g., values-es, values-fr) for target locales.
- Keep keys identical; only translate values.

5) Android 13+ per-app language (optional)
- Create res/xml/locales_config.xml listing supported locales.
- Reference in AndroidManifest.xml via android:localeConfig="@xml/locales_config".
- For in-app language picker, call AppCompatDelegate.setApplicationLocales(...) and persist selection.

6) Locale-sensitive formatting
- Prefer DateFormat / NumberFormat for locale-aware formatting and respect 12/24h settings.

7) Accessibility
- Ensure all Icon/IconButton/Image have meaningful contentDescription using string resources.
- Use semantics for non-textual interactive elements where needed.

8) QA
- Test by switching device language; verify truncation, RTL mirroring, and TalkBack.
- Maintain a translation workflow (spreadsheet/service) with key ownership and review.


10 changes: 5 additions & 5 deletions app/src/main/java/com/bitchat/android/geohash/LocationChannel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ package com.bitchat.android.geohash
* Direct port from iOS implementation for 100% compatibility
*/
enum class GeohashChannelLevel(val precision: Int, val displayName: String) {
BLOCK(7, "Block"),
NEIGHBORHOOD(6, "Neighborhood"),
CITY(5, "City"),
PROVINCE(4, "Province"),
REGION(2, "REGION");
BLOCK(7, "block"),
NEIGHBORHOOD(6, "neighborhood"),
CITY(5, "city"),
PROVINCE(4, "province"),
REGION(2, "region");

companion object {
fun allCases(): List<GeohashChannelLevel> = values().toList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ private fun BatteryOptimizationEnabledContent(

Icon(
imageVector = Icons.Outlined.BatteryAlert,
contentDescription = "Battery Optimization",
contentDescription = stringResource(id = R.string.content_desc_battery_optimization),
modifier = Modifier.size(64.dp),
tint = colorScheme.error
)
Expand Down Expand Up @@ -231,7 +231,7 @@ private fun BatteryOptimizationCheckingContent(

Icon(
imageVector = Icons.Filled.BatteryStd,
contentDescription = "Checking Battery Optimization",
contentDescription = stringResource(id = R.string.content_desc_checking_battery_optimization),
modifier = Modifier
.size(64.dp)
.rotate(rotation),
Expand Down Expand Up @@ -277,7 +277,7 @@ private fun BatteryOptimizationNotSupportedContent(

Icon(
imageVector = Icons.Filled.CheckCircle,
contentDescription = "Battery Optimization Not Supported",
contentDescription = stringResource(id = R.string.content_desc_battery_optimization_not_supported),
modifier = Modifier.size(64.dp),
tint = colorScheme.primary
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.res.stringResource
import com.bitchat.android.R

/**
* Screen shown when checking Bluetooth status or requesting Bluetooth enable
Expand Down Expand Up @@ -69,7 +71,7 @@ private fun BluetoothDisabledContent(
// Bluetooth icon - using Bluetooth outlined icon in app's green color
Icon(
imageVector = Icons.Outlined.Bluetooth,
contentDescription = "Bluetooth",
contentDescription = stringResource(id = R.string.content_desc_bluetooth),
modifier = Modifier.size(64.dp),
tint = Color(0xFF00C851) // App's main green color
)
Expand Down Expand Up @@ -184,7 +186,7 @@ private fun BluetoothNotSupportedContent(
}

Text(
text = "Bluetooth Not Supported",
text = stringResource(id = R.string.bt_not_supported),
style = MaterialTheme.typography.headlineSmall.copy(
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
Expand All @@ -201,7 +203,7 @@ private fun BluetoothNotSupportedContent(
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Text(
text = "This device doesn't support Bluetooth Low Energy (BLE), which is required for bitchat to function.\n\nbitchat needs BLE to create mesh networks and communicate with nearby devices without internet.",
text = stringResource(id = R.string.bt_not_supported_paragraph),
style = MaterialTheme.typography.bodyMedium.copy(
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface
Expand All @@ -222,7 +224,7 @@ private fun BluetoothCheckingContent(
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "bitchat",
text = stringResource(id = R.string.app_name),
style = MaterialTheme.typography.headlineLarge.copy(
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
Expand All @@ -234,7 +236,7 @@ private fun BluetoothCheckingContent(
BluetoothLoadingIndicator()

Text(
text = "Checking Bluetooth status...",
text = stringResource(id = R.string.checking_bluetooth_status),
style = MaterialTheme.typography.bodyLarge.copy(
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.7f)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.res.stringResource
import com.bitchat.android.R
import androidx.compose.ui.unit.dp

/**
Expand Down Expand Up @@ -59,7 +61,7 @@ fun InitializingScreen(modifier: Modifier) {
) {
// App title
Text(
text = "bitchat",
text = stringResource(id = R.string.app_name),
style = MaterialTheme.typography.headlineLarge.copy(
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
Expand Down Expand Up @@ -88,7 +90,7 @@ fun InitializingScreen(modifier: Modifier) {
horizontalArrangement = Arrangement.Center
) {
Text(
text = "Initializing mesh network",
text = stringResource(id = R.string.initializing_mesh_network),
style = MaterialTheme.typography.bodyLarge.copy(
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.7f)
Expand Down Expand Up @@ -123,7 +125,7 @@ fun InitializingScreen(modifier: Modifier) {
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Setting up Bluetooth mesh networking...",
text = stringResource(id = R.string.setting_up_bluetooth_mesh),
style = MaterialTheme.typography.bodyMedium.copy(
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.8f)
Expand All @@ -132,7 +134,7 @@ fun InitializingScreen(modifier: Modifier) {
)

Text(
text = "This should only take a few seconds",
text = stringResource(id = R.string.initializing_note_seconds),
style = MaterialTheme.typography.bodySmall.copy(
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.6f)
Expand Down Expand Up @@ -180,7 +182,7 @@ fun InitializationErrorScreen(
}

Text(
text = "Setup Not Complete",
text = stringResource(id = R.string.setup_not_complete),
style = MaterialTheme.typography.headlineSmall.copy(
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
Expand Down Expand Up @@ -216,7 +218,7 @@ fun InitializationErrorScreen(
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "Try Again",
text = stringResource(id = R.string.try_again),
style = MaterialTheme.typography.bodyMedium.copy(
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold
Expand All @@ -230,7 +232,7 @@ fun InitializationErrorScreen(
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "Open Settings",
text = stringResource(id = R.string.open_settings),
style = MaterialTheme.typography.bodyMedium.copy(
fontFamily = FontFamily.Monospace
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.res.stringResource
import com.bitchat.android.R

/**
* Screen shown when checking location services status or requesting location services enable
Expand Down Expand Up @@ -70,13 +72,13 @@ private fun LocationDisabledContent(
// Location icon - using LocationOn outlined icon in app's green color
Icon(
imageVector = Icons.Outlined.LocationOn,
contentDescription = "Location Services",
contentDescription = stringResource(id = R.string.content_desc_location_services),
modifier = Modifier.size(64.dp),
tint = Color(0xFF00C851) // App's main green color
)

Text(
text = "Location Services Required",
text = stringResource(id = R.string.location_services_required),
style = MaterialTheme.typography.headlineSmall.copy(
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
Expand All @@ -103,13 +105,13 @@ private fun LocationDisabledContent(
) {
Icon(
imageVector = Icons.Filled.Security,
contentDescription = "Privacy",
contentDescription = stringResource(id = R.string.content_desc_privacy),
tint = Color(0xFF4CAF50),
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Privacy First",
text = stringResource(id = R.string.privacy_first),
style = MaterialTheme.typography.bodyMedium.copy(
fontWeight = FontWeight.Bold,
color = colorScheme.onSurface
Expand All @@ -118,7 +120,7 @@ private fun LocationDisabledContent(
}

Text(
text = "bitchat does NOT track your location.\n\nLocation services are required for Bluetooth scanning and for the Geohash chat feature.",
text = stringResource(id = R.string.loc_bitchat_does_not_track),
style = MaterialTheme.typography.bodySmall.copy(
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.8f)
Expand All @@ -128,7 +130,7 @@ private fun LocationDisabledContent(
Spacer(modifier = Modifier.height(4.dp))

Text(
text = "bitchat needs location services for:",
text = stringResource(id = R.string.loc_needs),
style = MaterialTheme.typography.bodyMedium.copy(
fontWeight = FontWeight.Medium,
color = colorScheme.onSurface
Expand All @@ -138,10 +140,10 @@ private fun LocationDisabledContent(
)

Text(
text = "• Bluetooth device scanning\n" +
"• Discovering nearby users on mesh network\n" +
"• Geohash chat feature\n" +
"• No tracking or location collection",
text = stringResource(id = R.string.loc_bullet_1) + "\n" +
stringResource(id = R.string.loc_bullet_2) + "\n" +
stringResource(id = R.string.loc_bullet_3) + "\n" +
stringResource(id = R.string.loc_bullet_4),
style = MaterialTheme.typography.bodySmall.copy(
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.8f)
Expand All @@ -165,7 +167,7 @@ private fun LocationDisabledContent(
)
) {
Text(
text = "Open Location Settings",
text = stringResource(id = R.string.open_location_settings),
style = MaterialTheme.typography.bodyMedium.copy(
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold
Expand All @@ -179,7 +181,7 @@ private fun LocationDisabledContent(
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "Check Again",
text = stringResource(id = R.string.check_again),
style = MaterialTheme.typography.bodyMedium.copy(
fontFamily = FontFamily.Monospace
),
Expand All @@ -202,13 +204,13 @@ private fun LocationNotAvailableContent(
// Error icon
Icon(
imageVector = Icons.Filled.ErrorOutline,
contentDescription = "Error",
contentDescription = stringResource(id = R.string.content_desc_error),
modifier = Modifier.size(64.dp),
tint = colorScheme.error
)

Text(
text = "Location Services Unavailable",
text = stringResource(id = R.string.loc_unavailable),
style = MaterialTheme.typography.headlineSmall.copy(
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
Expand All @@ -225,7 +227,7 @@ private fun LocationNotAvailableContent(
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Text(
text = "Location services are not available on this device. This is unusual as location services are standard on Android devices.\n\nbitchat needs location services for Bluetooth scanning to work properly (Android requirement). Without this, the app cannot discover nearby users.",
text = stringResource(id = R.string.loc_unavailable_paragraph),
style = MaterialTheme.typography.bodyMedium.copy(
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.res.stringResource
import com.bitchat.android.R

/**
* Permission explanation screen shown before requesting permissions
Expand Down Expand Up @@ -46,7 +48,7 @@ fun PermissionExplanationScreen(
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Welcome to bitchat",
text = stringResource(id = R.string.welcome_to_app, stringResource(id = R.string.app_name)),
style = MaterialTheme.typography.headlineMedium.copy(
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Bold,
Expand All @@ -58,7 +60,7 @@ fun PermissionExplanationScreen(
Spacer(modifier = Modifier.height(8.dp))

Text(
text = "Decentralized mesh messaging over Bluetooth",
text = stringResource(id = R.string.decentralized_mesh_bluetooth),
style = MaterialTheme.typography.bodyMedium.copy(
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.7f)
Expand Down Expand Up @@ -91,7 +93,7 @@ fun PermissionExplanationScreen(
modifier = Modifier.size(20.dp)
)
Text(
text = "Your Privacy is Protected",
text = stringResource(id = R.string.privacy_protected),
style = MaterialTheme.typography.titleSmall.copy(
fontWeight = FontWeight.Bold,
color = colorScheme.onSurface
Expand All @@ -100,10 +102,7 @@ fun PermissionExplanationScreen(
}

Text(
text = "• bitchat doesn't track you or collect personal data\n" +
"• Bluetooth mesh chats are fully offline and require no internet\n" +
"• Geohash chats use the internet but your location is generalized\n" +
"• Your messages stay on your device and peer devices only",
text = stringResource(id = R.string.privacy_points, stringResource(id = R.string.app_name)),
style = MaterialTheme.typography.bodySmall.copy(
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.8f)
Expand All @@ -115,7 +114,7 @@ fun PermissionExplanationScreen(
Spacer(modifier = Modifier.height(8.dp))

Text(
text = "To work properly, bitchat needs these permissions:",
text = stringResource(id = R.string.needs_permissions_intro, stringResource(id = R.string.app_name)),
style = MaterialTheme.typography.bodyMedium.copy(
fontWeight = FontWeight.Medium,
color = colorScheme.onSurface
Expand Down Expand Up @@ -220,7 +219,7 @@ private fun PermissionCategoryCard(
modifier = Modifier.size(16.dp)
)
Text(
text = "bitchat does NOT track your location",
text = stringResource(id = R.string.does_not_track_location, stringResource(id = R.string.app_name)),
style = MaterialTheme.typography.bodySmall.copy(
fontFamily = FontFamily.Monospace,
fontWeight = FontWeight.Medium,
Expand Down
Loading