From 46cd22b2e4b9b4d9d9b7dac36736c9df95d40250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Orzech?= Date: Sat, 31 Jan 2026 12:16:03 +0100 Subject: [PATCH] Add settings screen with localization support for 20 languages Introduces a new Settings screen with language picker, contact/feedback button, and logout functionality. All UI strings now use centralized string resources with translations for English, Chinese, Spanish, Hindi, Arabic, Portuguese, Bengali, Russian, Japanese, French, German, Korean, Italian, Turkish, Vietnamese, Polish, Ukrainian, Dutch, Thai, and Indonesian. Uses AppCompatDelegate for runtime locale switching without app restart. Also adds CLAUDE.md for Claude Code guidance. --- .claude/.DS_Store | Bin 0 -> 8196 bytes .claude/commands/commit-push-pr.md | 15 + .claude/commands/new-version.md | 15 + CLAUDE.md | 84 +++++ README.md | 14 +- app/build.gradle.kts | 2 + .../main/java/com/fastmask/MainActivity.kt | 21 +- .../fastmask/data/local/SettingsDataStore.kt | 44 +++ .../com/fastmask/domain/model/Language.kt | 35 ++ .../usecase/GetCurrentLanguageUseCase.kt | 21 ++ .../domain/usecase/SetLanguageUseCase.kt | 23 ++ .../java/com/fastmask/ui/auth/LoginScreen.kt | 26 +- .../fastmask/ui/components/ErrorMessage.kt | 4 +- .../fastmask/ui/components/MaskedEmailCard.kt | 11 +- .../ui/create/CreateMaskedEmailScreen.kt | 42 ++- .../ui/detail/MaskedEmailDetailScreen.kt | 68 ++-- .../fastmask/ui/list/MaskedEmailListScreen.kt | 80 +++-- .../fastmask/ui/navigation/FastMaskNavHost.kt | 18 +- .../com/fastmask/ui/navigation/NavRoutes.kt | 1 + .../fastmask/ui/settings/SettingsScreen.kt | 304 ++++++++++++++++++ .../fastmask/ui/settings/SettingsViewModel.kt | 66 ++++ app/src/main/res/values-ar/strings.xml | 121 +++++++ app/src/main/res/values-bn/strings.xml | 121 +++++++ app/src/main/res/values-de/strings.xml | 121 +++++++ app/src/main/res/values-es/strings.xml | 121 +++++++ app/src/main/res/values-fr/strings.xml | 121 +++++++ app/src/main/res/values-hi/strings.xml | 121 +++++++ app/src/main/res/values-id/strings.xml | 121 +++++++ app/src/main/res/values-it/strings.xml | 121 +++++++ app/src/main/res/values-ja/strings.xml | 121 +++++++ app/src/main/res/values-ko/strings.xml | 121 +++++++ app/src/main/res/values-nl/strings.xml | 121 +++++++ app/src/main/res/values-pl/strings.xml | 121 +++++++ app/src/main/res/values-pt/strings.xml | 121 +++++++ app/src/main/res/values-ru/strings.xml | 121 +++++++ app/src/main/res/values-th/strings.xml | 121 +++++++ app/src/main/res/values-tr/strings.xml | 121 +++++++ app/src/main/res/values-uk/strings.xml | 121 +++++++ app/src/main/res/values-vi/strings.xml | 121 +++++++ app/src/main/res/values-zh-rCN/strings.xml | 121 +++++++ app/src/main/res/values/strings.xml | 117 +++++++ app/src/main/res/xml/locales_config.xml | 19 ++ 42 files changed, 3233 insertions(+), 96 deletions(-) create mode 100644 .claude/.DS_Store create mode 100644 .claude/commands/commit-push-pr.md create mode 100644 .claude/commands/new-version.md create mode 100644 CLAUDE.md create mode 100644 app/src/main/java/com/fastmask/data/local/SettingsDataStore.kt create mode 100644 app/src/main/java/com/fastmask/domain/model/Language.kt create mode 100644 app/src/main/java/com/fastmask/domain/usecase/GetCurrentLanguageUseCase.kt create mode 100644 app/src/main/java/com/fastmask/domain/usecase/SetLanguageUseCase.kt create mode 100644 app/src/main/java/com/fastmask/ui/settings/SettingsScreen.kt create mode 100644 app/src/main/java/com/fastmask/ui/settings/SettingsViewModel.kt create mode 100644 app/src/main/res/values-ar/strings.xml create mode 100644 app/src/main/res/values-bn/strings.xml create mode 100644 app/src/main/res/values-de/strings.xml create mode 100644 app/src/main/res/values-es/strings.xml create mode 100644 app/src/main/res/values-fr/strings.xml create mode 100644 app/src/main/res/values-hi/strings.xml create mode 100644 app/src/main/res/values-id/strings.xml create mode 100644 app/src/main/res/values-it/strings.xml create mode 100644 app/src/main/res/values-ja/strings.xml create mode 100644 app/src/main/res/values-ko/strings.xml create mode 100644 app/src/main/res/values-nl/strings.xml create mode 100644 app/src/main/res/values-pl/strings.xml create mode 100644 app/src/main/res/values-pt/strings.xml create mode 100644 app/src/main/res/values-ru/strings.xml create mode 100644 app/src/main/res/values-th/strings.xml create mode 100644 app/src/main/res/values-tr/strings.xml create mode 100644 app/src/main/res/values-uk/strings.xml create mode 100644 app/src/main/res/values-vi/strings.xml create mode 100644 app/src/main/res/values-zh-rCN/strings.xml diff --git a/.claude/.DS_Store b/.claude/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..e6c3e18687f99c6e6cb6adec200bd8e973a66a30 GIT binary patch literal 8196 zcmeHMTWl0n7(U;$&>3-H%C*1%Lst}}NP!mRX13kZP(a$Sg>ElUW_L#znVl&+yItfW zHC`eTHPL7cUY;c05}v%E#zccgAJiDE7~_k#m>3^SH0lc&|1)QnKuh1yK%BFg^PThm z=RarufBx^xo?XTmnv21xElv zegp^#wdouX*cc(kLmUzqn9`i$^nlP5;SmGEob(1CP8bhyNT4uh5atZw$_P&=2v#RM zBN)yQ7Z}x11fmFxjsT~R%reYlIr?05eos4Insleq1CH$%>g&%!s;C?{zN$*8nozYP zxyLId3ud9{H=Frh-s|){CtYrv>Fu^v>er?wJhx!lu9XiA?5ssY`nqg4>y?_lqMP*t z+oVt^imdc&H3ttiG}PA_jVl@t*BJ*_EvI(Fij{{CEAq71(luNA_c}S(dsw)Fa64e? z2-l8X&M(IwObEX!(q+HC3H_3l>n2>UPSjJ~sh-|GwO^Y&NG`Mw6l~A+w_ACiuwR?x zldEkR&+W)tT;G-PoMO)P`?czf>12wIS+JTMCutwB{2p_Er?tPp+uSgd_Z+9g^KCw3 zcd9U!D%InA`9dO?H)YdOVgjw`TU#xf-yXDDXcd3z2#=whoK#~hSai#Z+txL;Zfs8u zYt@sdOr55xJrrKIVBcw5`E4aTTiDZW`Weg3+V1YILEE>}j@9X!Ijb6ViZVQN)@)6m zqt7d=Mw-Z4OimTk_P(NBxO;d?^<+)0)o)O=*X6z}-PavnmamizG}ZH}3?93fh*u9dY-O4p!x96~rv zjmt_Zw1sB z$IM&pB}!K9j4TK84w45Coiu!&^;x^%pww)4&?Y9(LluOOWjPkVSv;Ir`5DFD z+bx9GWm{O9_;-Xo#E!EQ>?C`ionc?F@7XWxPj(LDF%eTR9Wzja z`B;RdScTPCi*;y4J36oxN$i4+yRa7}4B=kfhx>69kK+kEiKp-kUcd>wh?j5@Z{if* z#ydETPw*)|!&mqg-{A-Rh~IGz=kd2BOVyGt&6Va!hO|gpBdwLzNv%@1)FbsueUc@) zQb{@_Js@0=$_(6D_BE70NH}U|AqEZz21@Ua!9Zg}+lGysHeZtr{Cbk$-1HeW^KV>S z-?-}b<_noA61kka7jq(@!6oDgXb_~zWl~t5GgmdX5l^iBBe_}TtX9RjEc1ldYh$XR zjUy(B?oA7+JDyl1x^+toLz9TSqPuLlp=uRG&0rtUS1zG0!p`b7s;X5INtI!pIHS&0 zn-oQ3!6Jzknub&E3MT(g>{s>&I}e4De>My(Km#Rw0&SG+z1TqsZz4?zpM!@1?85;Z z!aX=b>3yT literal 0 HcmV?d00001 diff --git a/.claude/commands/commit-push-pr.md b/.claude/commands/commit-push-pr.md new file mode 100644 index 0000000..69009d5 --- /dev/null +++ b/.claude/commands/commit-push-pr.md @@ -0,0 +1,15 @@ +--- +allowed-tools: Bash(git add:*), Bash(git status:*), Bash(git commit:*) +description: Create a git commit +--- + +## Context + +- Current git status: !`git status` +- Current git diff (staged and unstaged changes): !`git diff HEAD` +- Current branch: !`git branch --show-current` +- Recent commits: !`git log --oneline -10` + +## Your task + +Based on the above changes, create a single git commit. Then push it. diff --git a/.claude/commands/new-version.md b/.claude/commands/new-version.md new file mode 100644 index 0000000..d52b9fa --- /dev/null +++ b/.claude/commands/new-version.md @@ -0,0 +1,15 @@ +--- +allowed-tools: Bash(git add:*), Bash(git status:*), Bash(git commit:*) +description: Do migration tests first. Then: update changelog, update readme, update version in gradle files, create a git commit and push it +--- + +## Context + +- Current git status: !`git status` +- Current git diff (staged and unstaged changes): !`git diff HEAD` +- Current branch: !`git branch --show-current` +- Recent commits: !`git log --oneline -10` + +## Your task + +Do migration tests first. Then: update changelog, update readme, update version in gradle files, create a git commit and push it diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..3578034 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,84 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build Commands + +```bash +# Build debug APK +./gradlew assembleDebug + +# Build release APK (with ProGuard/R8 minification) +./gradlew assembleRelease + +# Run unit tests +./gradlew test + +# Run instrumented tests (requires emulator/device) +./gradlew connectedAndroidTest + +# Clean build +./gradlew clean +``` + +APK outputs: `app/build/outputs/apk/` + +## Project Configuration + +- **SDK**: Compile/Target 34, Min 26 +- **JDK**: 17 +- **Kotlin**: 1.9.22 +- **Package**: `com.fastmask` + +## Architecture + +Clean Architecture with MVVM pattern. Three distinct layers: + +### Data Layer (`data/`) +- `api/` - JMAP protocol integration (Fastmail's native API) + - `JmapApi.kt` - API client with session caching + - `JmapService.kt` - Retrofit service interface + - `JmapModels.kt` - Kotlinx Serialization models +- `local/` - Persistence + - `TokenStorage.kt` - EncryptedSharedPreferences (lazy-initialized for Hilt compatibility) + - `SettingsDataStore.kt` - DataStore for language preferences +- `repository/` - Repository implementations + +### Domain Layer (`domain/`) +- `model/` - Domain models (`MaskedEmail`, `Language`, `EmailState` enum) +- `repository/` - Abstract interfaces +- `usecase/` - Business logic (Login, Logout, CRUD for masked emails, language settings) + +### UI Layer (`ui/`) +- `auth/`, `list/`, `create/`, `detail/`, `settings/` - Feature screens with ViewModels +- `components/` - Reusable composables (MaskedEmailCard, ShimmerEffect, ErrorMessage) +- `navigation/` - Jetpack Navigation with shared element transitions +- `theme/` - Material 3 theming with dynamic colors + +## Key Patterns + +**State Management**: `StateFlow` for reactive state, `SharedFlow` for one-time events (navigation, logout) + +**Dependency Injection**: Hilt with `NetworkModule` (Retrofit, OkHttp, Json) and `RepositoryModule` (repository bindings) + +**API Protocol**: JMAP (JSON Mail Access Protocol) with Bearer token auth. Session (accountId, apiUrl) is cached after first call. + +**Security**: Tokens stored in EncryptedSharedPreferences using Android Security Crypto library. TokenStorage uses lazy initialization to prevent Hilt injection issues. + +## Localization + +20 languages supported. String resources in `res/values-*/strings.xml`. In-app language override uses AppCompatDelegate for runtime switching without restart. + +## ProGuard/R8 + +Release builds use minification. Key rules in `app/proguard-rules.pro`: +- Keep Kotlinx Serialization and JMAP models +- Keep Google Tink classes (security-crypto dependency) +- Retrofit and OkHttp configurations + +## Commit Message Format + +- `Add: new feature description` +- `Fix: bug description` +- `Update: what was changed` +- `Refactor: what was refactored` diff --git a/README.md b/README.md index 923f6b2..47967d7 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ FastMask is a native Android application that lets you manage your [Fastmail](ht | **Delete** | Remove masks you no longer need | | **Search & Filter** | Find specific masks instantly | | **Material You** | Dynamic theming that adapts to your wallpaper | +| **20 Languages** | Full localization with in-app language picker | +| **Settings** | Language selection, contact/feedback, and logout | ## Screenshots @@ -151,6 +153,7 @@ app/ ├── list/ # Masked email list ├── create/ # Create new mask ├── detail/ # View/edit mask details + ├── settings/ # Settings screen ├── components/ # Reusable UI components ├── navigation/ # Navigation setup └── theme/ # Material 3 theming @@ -188,6 +191,12 @@ Contributions are welcome! Here's how you can help: ## Changelog +### v1.3 (January 2026) +- **New**: Settings screen with language picker, contact button, and logout +- **New**: Localization support for 20 languages (English, Chinese, Spanish, Hindi, Arabic, Portuguese, Bengali, Russian, Japanese, French, German, Korean, Italian, Turkish, Vietnamese, Polish, Ukrainian, Dutch, Thai, Indonesian) +- **New**: In-app language override using AppCompatDelegate +- **Improved**: All UI strings now use centralized string resources + ### v1.2 (January 2026) - **Fixed**: Login crash caused by `ParameterizedType` casting error at runtime - **Improved**: TokenStorage now uses lazy initialization for EncryptedSharedPreferences @@ -207,11 +216,10 @@ Contributions are welcome! Here's how you can help: ## Roadmap - [ ] Add screenshots to README -- [ ] Biometric authentication option - [ ] Widget for quick mask creation -- [ ] Export/import functionality - [ ] Dark/light mode toggle -- [ ] Localization support +- [x] Localization support (20 languages) +- [x] Settings screen with language picker ## License diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 90a0695..7dbe926 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -54,6 +54,7 @@ android { buildFeatures { compose = true + buildConfig = true } composeOptions { @@ -73,6 +74,7 @@ dependencies { implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") implementation("androidx.activity:activity-compose:1.9.0") + implementation("androidx.appcompat:appcompat:1.7.0") // Compose implementation(platform("androidx.compose:compose-bom:2024.09.00")) diff --git a/app/src/main/java/com/fastmask/MainActivity.kt b/app/src/main/java/com/fastmask/MainActivity.kt index d31fe89..f0da3fd 100644 --- a/app/src/main/java/com/fastmask/MainActivity.kt +++ b/app/src/main/java/com/fastmask/MainActivity.kt @@ -1,15 +1,18 @@ package com.fastmask import android.os.Bundle -import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.ui.Modifier +import androidx.core.os.LocaleListCompat import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.navigation.compose.rememberNavController +import com.fastmask.data.local.SettingsDataStore import com.fastmask.domain.repository.AuthRepository import com.fastmask.ui.navigation.FastMaskNavHost import com.fastmask.ui.navigation.NavRoutes @@ -18,14 +21,20 @@ import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @AndroidEntryPoint -class MainActivity : ComponentActivity() { +class MainActivity : AppCompatActivity() { @Inject lateinit var authRepository: AuthRepository + @Inject + lateinit var settingsDataStore: SettingsDataStore + private var isReady = false override fun onCreate(savedInstanceState: Bundle?) { + // Restore saved language before anything else + restoreSavedLanguage() + val splashScreen = installSplashScreen() super.onCreate(savedInstanceState) @@ -56,4 +65,12 @@ class MainActivity : ComponentActivity() { } } } + + private fun restoreSavedLanguage() { + val savedLanguageCode = settingsDataStore.getLanguageBlocking() + if (savedLanguageCode != null) { + val localeList = LocaleListCompat.forLanguageTags(savedLanguageCode) + AppCompatDelegate.setApplicationLocales(localeList) + } + } } diff --git a/app/src/main/java/com/fastmask/data/local/SettingsDataStore.kt b/app/src/main/java/com/fastmask/data/local/SettingsDataStore.kt new file mode 100644 index 0000000..85edc60 --- /dev/null +++ b/app/src/main/java/com/fastmask/data/local/SettingsDataStore.kt @@ -0,0 +1,44 @@ +package com.fastmask.data.local + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.runBlocking +import javax.inject.Inject +import javax.inject.Singleton + +private val Context.dataStore: DataStore by preferencesDataStore(name = "settings") + +@Singleton +class SettingsDataStore @Inject constructor( + @ApplicationContext private val context: Context +) { + private val languageKey = stringPreferencesKey("language_code") + + val languageFlow: Flow = context.dataStore.data.map { preferences -> + preferences[languageKey] + } + + suspend fun setLanguage(languageCode: String?) { + context.dataStore.edit { preferences -> + if (languageCode == null) { + preferences.remove(languageKey) + } else { + preferences[languageKey] = languageCode + } + } + } + + fun getLanguageBlocking(): String? { + return runBlocking { + context.dataStore.data.first()[languageKey] + } + } +} diff --git a/app/src/main/java/com/fastmask/domain/model/Language.kt b/app/src/main/java/com/fastmask/domain/model/Language.kt new file mode 100644 index 0000000..e78b688 --- /dev/null +++ b/app/src/main/java/com/fastmask/domain/model/Language.kt @@ -0,0 +1,35 @@ +package com.fastmask.domain.model + +import com.fastmask.R + +enum class Language( + val code: String, + val displayNameRes: Int +) { + ENGLISH("en", R.string.language_en), + CHINESE("zh", R.string.language_zh), + SPANISH("es", R.string.language_es), + HINDI("hi", R.string.language_hi), + ARABIC("ar", R.string.language_ar), + PORTUGUESE("pt", R.string.language_pt), + BENGALI("bn", R.string.language_bn), + RUSSIAN("ru", R.string.language_ru), + JAPANESE("ja", R.string.language_ja), + FRENCH("fr", R.string.language_fr), + GERMAN("de", R.string.language_de), + KOREAN("ko", R.string.language_ko), + ITALIAN("it", R.string.language_it), + TURKISH("tr", R.string.language_tr), + VIETNAMESE("vi", R.string.language_vi), + POLISH("pl", R.string.language_pl), + UKRAINIAN("uk", R.string.language_uk), + DUTCH("nl", R.string.language_nl), + THAI("th", R.string.language_th), + INDONESIAN("id", R.string.language_id); + + companion object { + fun fromCode(code: String?): Language? { + return entries.find { it.code == code } + } + } +} diff --git a/app/src/main/java/com/fastmask/domain/usecase/GetCurrentLanguageUseCase.kt b/app/src/main/java/com/fastmask/domain/usecase/GetCurrentLanguageUseCase.kt new file mode 100644 index 0000000..1fc63ee --- /dev/null +++ b/app/src/main/java/com/fastmask/domain/usecase/GetCurrentLanguageUseCase.kt @@ -0,0 +1,21 @@ +package com.fastmask.domain.usecase + +import com.fastmask.data.local.SettingsDataStore +import com.fastmask.domain.model.Language +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class GetCurrentLanguageUseCase @Inject constructor( + private val settingsDataStore: SettingsDataStore +) { + operator fun invoke(): Flow { + return settingsDataStore.languageFlow.map { code -> + Language.fromCode(code) + } + } + + fun getBlocking(): Language? { + return Language.fromCode(settingsDataStore.getLanguageBlocking()) + } +} diff --git a/app/src/main/java/com/fastmask/domain/usecase/SetLanguageUseCase.kt b/app/src/main/java/com/fastmask/domain/usecase/SetLanguageUseCase.kt new file mode 100644 index 0000000..939826c --- /dev/null +++ b/app/src/main/java/com/fastmask/domain/usecase/SetLanguageUseCase.kt @@ -0,0 +1,23 @@ +package com.fastmask.domain.usecase + +import androidx.appcompat.app.AppCompatDelegate +import androidx.core.os.LocaleListCompat +import com.fastmask.data.local.SettingsDataStore +import com.fastmask.domain.model.Language +import javax.inject.Inject + +class SetLanguageUseCase @Inject constructor( + private val settingsDataStore: SettingsDataStore +) { + suspend operator fun invoke(language: Language?) { + val languageCode = language?.code + settingsDataStore.setLanguage(languageCode) + + val localeList = if (languageCode != null) { + LocaleListCompat.forLanguageTags(languageCode) + } else { + LocaleListCompat.getEmptyLocaleList() + } + AppCompatDelegate.setApplicationLocales(localeList) + } +} diff --git a/app/src/main/java/com/fastmask/ui/auth/LoginScreen.kt b/app/src/main/java/com/fastmask/ui/auth/LoginScreen.kt index 159bf10..151ec04 100644 --- a/app/src/main/java/com/fastmask/ui/auth/LoginScreen.kt +++ b/app/src/main/java/com/fastmask/ui/auth/LoginScreen.kt @@ -35,6 +35,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation @@ -42,6 +43,7 @@ import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import com.fastmask.R import kotlinx.coroutines.flow.collectLatest @Composable @@ -80,13 +82,13 @@ fun LoginScreen( Spacer(modifier = Modifier.height(16.dp)) Text( - text = "FastMask", + text = stringResource(R.string.app_name), style = MaterialTheme.typography.headlineLarge, color = MaterialTheme.colorScheme.primary ) Text( - text = "Fastmail Masked Email Manager", + text = stringResource(R.string.login_subtitle), style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -96,8 +98,8 @@ fun LoginScreen( OutlinedTextField( value = uiState.token, onValueChange = viewModel::onTokenChange, - label = { Text("API Token") }, - placeholder = { Text("Enter your Fastmail API token") }, + label = { Text(stringResource(R.string.login_api_token_label)) }, + placeholder = { Text(stringResource(R.string.login_api_token_placeholder)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), visualTransformation = if (showToken) VisualTransformation.None else PasswordVisualTransformation(), @@ -112,7 +114,11 @@ fun LoginScreen( IconButton(onClick = { showToken = !showToken }) { Icon( imageVector = if (showToken) Icons.Default.VisibilityOff else Icons.Default.Visibility, - contentDescription = if (showToken) "Hide token" else "Show token" + contentDescription = if (showToken) { + stringResource(R.string.login_hide_token) + } else { + stringResource(R.string.login_show_token) + } ) } }, @@ -143,7 +149,7 @@ fun LoginScreen( strokeWidth = 2.dp ) } else { - Text("Login") + Text(stringResource(R.string.login_button)) } } @@ -159,17 +165,13 @@ fun LoginScreen( modifier = Modifier.padding(16.dp) ) { Text( - text = "How to get your API token:", + text = stringResource(R.string.login_instructions_title), style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) Spacer(modifier = Modifier.height(8.dp)) Text( - text = "1. Log in to Fastmail web app\n" + - "2. Go to Settings > Privacy & Security\n" + - "3. Click on Integrations > API tokens\n" + - "4. Create a new token with \"Masked Email\" scope\n" + - "5. Copy the token and paste it above", + text = stringResource(R.string.login_instructions_full), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Start diff --git a/app/src/main/java/com/fastmask/ui/components/ErrorMessage.kt b/app/src/main/java/com/fastmask/ui/components/ErrorMessage.kt index 621d701..49d462f 100644 --- a/app/src/main/java/com/fastmask/ui/components/ErrorMessage.kt +++ b/app/src/main/java/com/fastmask/ui/components/ErrorMessage.kt @@ -16,8 +16,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import com.fastmask.R @Composable fun ErrorMessage( @@ -48,7 +50,7 @@ fun ErrorMessage( if (onRetry != null) { Spacer(modifier = Modifier.height(24.dp)) Button(onClick = onRetry) { - Text("Retry") + Text(stringResource(R.string.error_retry)) } } } diff --git a/app/src/main/java/com/fastmask/ui/components/MaskedEmailCard.kt b/app/src/main/java/com/fastmask/ui/components/MaskedEmailCard.kt index 3bdeae5..763ccce 100644 --- a/app/src/main/java/com/fastmask/ui/components/MaskedEmailCard.kt +++ b/app/src/main/java/com/fastmask/ui/components/MaskedEmailCard.kt @@ -30,11 +30,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import com.fastmask.R import com.fastmask.domain.model.EmailState import com.fastmask.domain.model.MaskedEmail import com.fastmask.ui.theme.FastMaskStatusColors @@ -52,10 +53,10 @@ fun MaskedEmailCard( val haptic = LocalHapticFeedback.current val stateDescription = when (maskedEmail.state) { - EmailState.ENABLED -> "Enabled" - EmailState.DISABLED -> "Disabled" - EmailState.DELETED -> "Deleted" - EmailState.PENDING -> "Pending" + EmailState.ENABLED -> stringResource(R.string.state_enabled) + EmailState.DISABLED -> stringResource(R.string.state_disabled) + EmailState.DELETED -> stringResource(R.string.state_deleted) + EmailState.PENDING -> stringResource(R.string.state_pending) } with(sharedTransitionScope) { diff --git a/app/src/main/java/com/fastmask/ui/create/CreateMaskedEmailScreen.kt b/app/src/main/java/com/fastmask/ui/create/CreateMaskedEmailScreen.kt index 3674494..6d59fb5 100644 --- a/app/src/main/java/com/fastmask/ui/create/CreateMaskedEmailScreen.kt +++ b/app/src/main/java/com/fastmask/ui/create/CreateMaskedEmailScreen.kt @@ -41,10 +41,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import com.fastmask.R import com.fastmask.domain.model.EmailState import kotlinx.coroutines.flow.collectLatest @@ -59,13 +61,17 @@ fun CreateMaskedEmailScreen( val snackbarHostState = remember { SnackbarHostState() } val haptic = LocalHapticFeedback.current + val createdMessage = stringResource(R.string.create_email_created, "%s") + val copyAction = stringResource(R.string.create_email_copy_action) + val navigateBackDesc = stringResource(R.string.navigate_back) + LaunchedEffect(Unit) { viewModel.events.collectLatest { event -> when (event) { is CreateMaskedEmailEvent.Created -> { val result = snackbarHostState.showSnackbar( - message = "Created: ${event.email}", - actionLabel = "Copy", + message = createdMessage.replace("%s", event.email), + actionLabel = copyAction, duration = SnackbarDuration.Long ) if (result == SnackbarResult.ActionPerformed) { @@ -82,7 +88,7 @@ fun CreateMaskedEmailScreen( Scaffold( topBar = { TopAppBar( - title = { Text("Create Masked Email") }, + title = { Text(stringResource(R.string.create_email_title)) }, navigationIcon = { IconButton( onClick = { @@ -90,7 +96,7 @@ fun CreateMaskedEmailScreen( onNavigateBack() }, modifier = Modifier.semantics { - contentDescription = "Navigate back" + contentDescription = navigateBackDesc } ) { Icon( @@ -119,12 +125,12 @@ fun CreateMaskedEmailScreen( OutlinedTextField( value = uiState.emailPrefix, onValueChange = viewModel::onPrefixChange, - label = { Text("Email Prefix (optional)") }, - placeholder = { Text("e.g., mysite_shopping") }, + label = { Text(stringResource(R.string.create_email_prefix_label)) }, + placeholder = { Text(stringResource(R.string.create_email_prefix_placeholder)) }, supportingText = { Text( text = uiState.prefixError - ?: "Max 64 chars: lowercase letters, numbers, underscores" + ?: stringResource(R.string.create_email_prefix_hint) ) }, isError = uiState.prefixError != null, @@ -138,8 +144,8 @@ fun CreateMaskedEmailScreen( OutlinedTextField( value = uiState.forDomain, onValueChange = viewModel::onDomainChange, - label = { Text("Associated Domain (optional)") }, - placeholder = { Text("e.g., example.com") }, + label = { Text(stringResource(R.string.create_email_domain_label)) }, + placeholder = { Text(stringResource(R.string.create_email_domain_placeholder)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), enabled = !uiState.isLoading @@ -150,8 +156,8 @@ fun CreateMaskedEmailScreen( OutlinedTextField( value = uiState.description, onValueChange = viewModel::onDescriptionChange, - label = { Text("Description (optional)") }, - placeholder = { Text("e.g., Shopping account") }, + label = { Text(stringResource(R.string.create_email_description_label)) }, + placeholder = { Text(stringResource(R.string.create_email_description_placeholder)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), enabled = !uiState.isLoading @@ -162,8 +168,8 @@ fun CreateMaskedEmailScreen( OutlinedTextField( value = uiState.url, onValueChange = viewModel::onUrlChange, - label = { Text("URL (optional)") }, - placeholder = { Text("e.g., https://example.com") }, + label = { Text(stringResource(R.string.create_email_url_label)) }, + placeholder = { Text(stringResource(R.string.create_email_url_placeholder)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), enabled = !uiState.isLoading @@ -172,7 +178,7 @@ fun CreateMaskedEmailScreen( Spacer(modifier = Modifier.height(24.dp)) Text( - text = "Initial State", + text = stringResource(R.string.create_email_initial_state), style = MaterialTheme.typography.titleMedium ) @@ -192,7 +198,11 @@ fun CreateMaskedEmailScreen( enabled = !uiState.isLoading ) Text( - text = state.name.lowercase().replaceFirstChar { it.uppercase() }, + text = when (state) { + EmailState.ENABLED -> stringResource(R.string.state_enabled) + EmailState.DISABLED -> stringResource(R.string.state_disabled) + else -> state.name.lowercase().replaceFirstChar { it.uppercase() } + }, style = MaterialTheme.typography.bodyLarge ) } @@ -224,7 +234,7 @@ fun CreateMaskedEmailScreen( strokeWidth = 2.dp ) } else { - Text("Create Masked Email") + Text(stringResource(R.string.create_email_button)) } } } diff --git a/app/src/main/java/com/fastmask/ui/detail/MaskedEmailDetailScreen.kt b/app/src/main/java/com/fastmask/ui/detail/MaskedEmailDetailScreen.kt index 5d4acf1..1af6672 100644 --- a/app/src/main/java/com/fastmask/ui/detail/MaskedEmailDetailScreen.kt +++ b/app/src/main/java/com/fastmask/ui/detail/MaskedEmailDetailScreen.kt @@ -48,7 +48,6 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar @@ -67,11 +66,13 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import com.fastmask.R import com.fastmask.domain.model.EmailState import com.fastmask.ui.components.ErrorMessage import com.fastmask.ui.components.LoadingIndicator @@ -94,18 +95,25 @@ fun MaskedEmailDetailScreen( val scope = rememberCoroutineScope() val haptic = LocalHapticFeedback.current + val updatedMessage = stringResource(R.string.email_detail_updated) + val deletedMessage = stringResource(R.string.email_detail_deleted) + val copiedMessage = stringResource(R.string.email_detail_copied) + val navigateBackDesc = stringResource(R.string.navigate_back) + val deleteEmailDesc = stringResource(R.string.email_detail_delete) + val copyEmailDesc = stringResource(R.string.email_detail_copy_email) + LaunchedEffect(Unit) { viewModel.events.collectLatest { event -> when (event) { is MaskedEmailDetailEvent.Updated -> { snackbarHostState.showSnackbar( - message = "Updated successfully", + message = updatedMessage, duration = SnackbarDuration.Short ) } is MaskedEmailDetailEvent.Deleted -> { snackbarHostState.showSnackbar( - message = "Deleted successfully", + message = deletedMessage, duration = SnackbarDuration.Short ) onNavigateBack() @@ -117,8 +125,8 @@ fun MaskedEmailDetailScreen( if (showDeleteDialog) { AlertDialog( onDismissRequest = { showDeleteDialog = false }, - title = { Text("Delete Masked Email") }, - text = { Text("Are you sure you want to delete this masked email? This action cannot be undone.") }, + title = { Text(stringResource(R.string.email_detail_delete_dialog_title)) }, + text = { Text(stringResource(R.string.email_detail_delete_dialog_message)) }, confirmButton = { TextButton( onClick = { @@ -129,12 +137,12 @@ fun MaskedEmailDetailScreen( contentColor = MaterialTheme.colorScheme.error ) ) { - Text("Delete") + Text(stringResource(R.string.email_detail_delete_confirm)) } }, dismissButton = { TextButton(onClick = { showDeleteDialog = false }) { - Text("Cancel") + Text(stringResource(R.string.email_detail_delete_cancel)) } } ) @@ -143,7 +151,7 @@ fun MaskedEmailDetailScreen( Scaffold( topBar = { TopAppBar( - title = { Text("Email Details") }, + title = { Text(stringResource(R.string.email_detail_title)) }, navigationIcon = { IconButton( onClick = { @@ -151,7 +159,7 @@ fun MaskedEmailDetailScreen( onNavigateBack() }, modifier = Modifier.semantics { - contentDescription = "Navigate back" + contentDescription = navigateBackDesc } ) { Icon( @@ -168,7 +176,7 @@ fun MaskedEmailDetailScreen( showDeleteDialog = true }, modifier = Modifier.semantics { - contentDescription = "Delete email" + contentDescription = deleteEmailDesc } ) { Icon( @@ -225,11 +233,12 @@ fun MaskedEmailDetailScreen( clipboard.setPrimaryClip(clip) scope.launch { snackbarHostState.showSnackbar( - message = "Copied to clipboard", + message = copiedMessage, duration = SnackbarDuration.Short ) } }, + copyEmailDesc = copyEmailDesc, sharedTransitionScope = sharedTransitionScope, animatedContentScope = animatedContentScope, modifier = Modifier.padding(paddingValues) @@ -250,6 +259,7 @@ private fun EmailDetailContent( onToggleState: () -> Unit, onSaveChanges: () -> Unit, onCopyEmail: (String) -> Unit, + copyEmailDesc: String, sharedTransitionScope: SharedTransitionScope, animatedContentScope: AnimatedContentScope, modifier: Modifier = Modifier @@ -323,7 +333,7 @@ private fun EmailDetailContent( onCopyEmail(email.email) }, modifier = Modifier.semantics { - contentDescription = "Copy email address" + contentDescription = copyEmailDesc } ) { Icon( @@ -339,14 +349,18 @@ private fun EmailDetailContent( verticalAlignment = Alignment.CenterVertically ) { Text( - text = "Status: ", + text = stringResource(R.string.email_detail_status), + style = MaterialTheme.typography.bodyMedium + ) + Text( + text = " ", style = MaterialTheme.typography.bodyMedium ) val statusText = when (email.state) { - EmailState.ENABLED -> "Enabled" - EmailState.DISABLED -> "Disabled" - EmailState.DELETED -> "Deleted" - EmailState.PENDING -> "Pending" + EmailState.ENABLED -> stringResource(R.string.state_enabled) + EmailState.DISABLED -> stringResource(R.string.state_disabled) + EmailState.DELETED -> stringResource(R.string.state_deleted) + EmailState.PENDING -> stringResource(R.string.state_pending) } Text( text = statusText, @@ -359,7 +373,7 @@ private fun EmailDetailContent( email.createdBy?.let { createdBy -> Spacer(modifier = Modifier.height(8.dp)) Text( - text = "Created by: $createdBy", + text = stringResource(R.string.email_detail_created_by, createdBy), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -368,7 +382,7 @@ private fun EmailDetailContent( email.formattedCreatedAt?.let { createdAt -> Spacer(modifier = Modifier.height(4.dp)) Text( - text = "Created: $createdAt", + text = stringResource(R.string.email_detail_created, createdAt), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -377,7 +391,7 @@ private fun EmailDetailContent( email.formattedLastMessageAt?.let { lastMessage -> Spacer(modifier = Modifier.height(4.dp)) Text( - text = "Last message: $lastMessage", + text = stringResource(R.string.email_detail_last_message, lastMessage), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant ) @@ -410,7 +424,7 @@ private fun EmailDetailContent( strokeWidth = 2.dp ) } else { - Text("Enable") + Text(stringResource(R.string.email_detail_enable)) } } } @@ -429,7 +443,7 @@ private fun EmailDetailContent( strokeWidth = 2.dp ) } else { - Text("Disable") + Text(stringResource(R.string.email_detail_disable)) } } } @@ -438,7 +452,7 @@ private fun EmailDetailContent( Spacer(modifier = Modifier.height(24.dp)) Text( - text = "Edit Details", + text = stringResource(R.string.email_detail_edit_title), style = MaterialTheme.typography.titleMedium ) @@ -447,7 +461,7 @@ private fun EmailDetailContent( OutlinedTextField( value = uiState.editedDescription, onValueChange = onDescriptionChange, - label = { Text("Description") }, + label = { Text(stringResource(R.string.email_detail_description_label)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), enabled = !uiState.isUpdating @@ -458,7 +472,7 @@ private fun EmailDetailContent( OutlinedTextField( value = uiState.editedForDomain, onValueChange = onForDomainChange, - label = { Text("Associated Domain") }, + label = { Text(stringResource(R.string.email_detail_domain_label)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), enabled = !uiState.isUpdating @@ -469,7 +483,7 @@ private fun EmailDetailContent( OutlinedTextField( value = uiState.editedUrl, onValueChange = onUrlChange, - label = { Text("URL") }, + label = { Text(stringResource(R.string.email_detail_url_label)) }, singleLine = true, modifier = Modifier.fillMaxWidth(), enabled = !uiState.isUpdating @@ -501,7 +515,7 @@ private fun EmailDetailContent( strokeWidth = 2.dp ) } else { - Text("Save Changes") + Text(stringResource(R.string.email_detail_save)) } } } diff --git a/app/src/main/java/com/fastmask/ui/list/MaskedEmailListScreen.kt b/app/src/main/java/com/fastmask/ui/list/MaskedEmailListScreen.kt index f684a3b..d656b68 100644 --- a/app/src/main/java/com/fastmask/ui/list/MaskedEmailListScreen.kt +++ b/app/src/main/java/com/fastmask/ui/list/MaskedEmailListScreen.kt @@ -21,8 +21,8 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.FilterList -import androidx.compose.material.icons.automirrored.filled.Logout import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api @@ -46,27 +46,30 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.repeatOnLifecycle import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp -import androidx.compose.ui.hapticfeedback.HapticFeedbackType -import androidx.compose.ui.platform.LocalHapticFeedback import androidx.hilt.navigation.compose.hiltViewModel +import com.fastmask.R import com.fastmask.domain.model.MaskedEmail import com.fastmask.ui.components.ErrorMessage import com.fastmask.ui.components.MaskedEmailCard import com.fastmask.ui.components.ShimmerEmailList -import kotlinx.coroutines.flow.collectLatest @OptIn(ExperimentalMaterial3Api::class, ExperimentalSharedTransitionApi::class) @Composable fun MaskedEmailListScreen( onNavigateToCreate: () -> Unit, onNavigateToDetail: (String) -> Unit, - onLogout: () -> Unit, + onNavigateToSettings: () -> Unit, sharedTransitionScope: SharedTransitionScope, animatedContentScope: AnimatedContentScope, viewModel: MaskedEmailListViewModel = hiltViewModel() @@ -77,28 +80,32 @@ fun MaskedEmailListScreen( val listState = rememberLazyListState() val haptic = LocalHapticFeedback.current - val isScrolling by remember { - derivedStateOf { listState.isScrollInProgress } - } - val expandedFab by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0 } } - LaunchedEffect(Unit) { - viewModel.events.collectLatest { event -> - when (event) { - is MaskedEmailListEvent.LoggedOut -> onLogout() - } + val isScrolling by remember { + derivedStateOf { listState.isScrollInProgress } + } + + val filterEmailsDesc = stringResource(R.string.email_list_filter_emails) + val settingsDesc = stringResource(R.string.email_list_settings) + val createDesc = stringResource(R.string.email_list_create_description) + + // Refresh the list when the screen resumes (e.g., after creating a new email) + val lifecycleOwner = LocalLifecycleOwner.current + LaunchedEffect(lifecycleOwner) { + lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewModel.loadMaskedEmails() } } Scaffold( topBar = { TopAppBar( - title = { Text("Masked Emails") }, + title = { Text(stringResource(R.string.email_list_title)) }, colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.surface, titleContentColor = MaterialTheme.colorScheme.onSurface @@ -111,7 +118,7 @@ fun MaskedEmailListScreen( showFilterMenu = true }, modifier = Modifier.semantics { - contentDescription = "Filter emails" + contentDescription = filterEmailsDesc } ) { Icon( @@ -125,7 +132,16 @@ fun MaskedEmailListScreen( ) { EmailFilter.entries.forEach { filter -> DropdownMenuItem( - text = { Text(filter.name.lowercase().replaceFirstChar { it.uppercase() }) }, + text = { + Text( + when (filter) { + EmailFilter.ALL -> stringResource(R.string.filter_all) + EmailFilter.ENABLED -> stringResource(R.string.filter_enabled) + EmailFilter.DISABLED -> stringResource(R.string.filter_disabled) + EmailFilter.DELETED -> stringResource(R.string.filter_deleted) + } + ) + }, onClick = { viewModel.onFilterChange(filter) showFilterMenu = false @@ -137,14 +153,14 @@ fun MaskedEmailListScreen( IconButton( onClick = { haptic.performHapticFeedback(HapticFeedbackType.LongPress) - viewModel.logout() + onNavigateToSettings() }, modifier = Modifier.semantics { - contentDescription = "Logout" + contentDescription = settingsDesc } ) { Icon( - imageVector = Icons.AutoMirrored.Filled.Logout, + imageVector = Icons.Default.Settings, contentDescription = null ) } @@ -164,11 +180,11 @@ fun MaskedEmailListScreen( contentDescription = null ) }, - text = { Text("Create") }, + text = { Text(stringResource(R.string.email_list_create_fab)) }, containerColor = MaterialTheme.colorScheme.tertiaryContainer, contentColor = MaterialTheme.colorScheme.onTertiaryContainer, modifier = Modifier.semantics { - contentDescription = "Create new masked email" + contentDescription = createDesc } ) } @@ -237,6 +253,7 @@ private fun M3SearchBar( modifier: Modifier = Modifier ) { val haptic = LocalHapticFeedback.current + val clearSearchDesc = stringResource(R.string.email_list_clear_search) SearchBar( inputField = { @@ -246,7 +263,7 @@ private fun M3SearchBar( onSearch = { onActiveChange(false) }, expanded = active, onExpandedChange = onActiveChange, - placeholder = { Text("Search emails...") }, + placeholder = { Text(stringResource(R.string.email_list_search_placeholder)) }, leadingIcon = { Icon( imageVector = Icons.Default.Search, @@ -263,7 +280,7 @@ private fun M3SearchBar( ) { Icon( imageVector = Icons.Default.Close, - contentDescription = "Clear search" + contentDescription = clearSearchDesc ) } } @@ -300,7 +317,16 @@ private fun FilterChips( haptic.performHapticFeedback(HapticFeedbackType.LongPress) onFilterSelected(filter) }, - label = { Text(filter.name.lowercase().replaceFirstChar { it.uppercase() }) } + label = { + Text( + when (filter) { + EmailFilter.ALL -> stringResource(R.string.filter_all) + EmailFilter.ENABLED -> stringResource(R.string.filter_enabled) + EmailFilter.DISABLED -> stringResource(R.string.filter_disabled) + EmailFilter.DELETED -> stringResource(R.string.filter_deleted) + } + ) + } ) } } @@ -329,7 +355,7 @@ private fun EmailList( contentAlignment = Alignment.Center ) { Text( - text = "No masked emails found", + text = stringResource(R.string.email_list_empty), style = MaterialTheme.typography.bodyLarge, color = MaterialTheme.colorScheme.onSurfaceVariant ) diff --git a/app/src/main/java/com/fastmask/ui/navigation/FastMaskNavHost.kt b/app/src/main/java/com/fastmask/ui/navigation/FastMaskNavHost.kt index 7b82a72..bc57b3b 100644 --- a/app/src/main/java/com/fastmask/ui/navigation/FastMaskNavHost.kt +++ b/app/src/main/java/com/fastmask/ui/navigation/FastMaskNavHost.kt @@ -17,6 +17,7 @@ import com.fastmask.ui.auth.LoginScreen import com.fastmask.ui.create.CreateMaskedEmailScreen import com.fastmask.ui.detail.MaskedEmailDetailScreen import com.fastmask.ui.list.MaskedEmailListScreen +import com.fastmask.ui.settings.SettingsScreen private const val TRANSITION_DURATION_MS = 300 @@ -79,13 +80,24 @@ fun FastMaskNavHost( onNavigateToDetail = { emailId -> navController.navigate(NavRoutes.emailDetail(emailId)) }, + onNavigateToSettings = { + navController.navigate(NavRoutes.SETTINGS) + }, + sharedTransitionScope = this@SharedTransitionLayout, + animatedContentScope = this@composable + ) + } + + composable(NavRoutes.SETTINGS) { + SettingsScreen( + onNavigateBack = { + navController.popBackStack() + }, onLogout = { navController.navigate(NavRoutes.LOGIN) { popUpTo(0) { inclusive = true } } - }, - sharedTransitionScope = this@SharedTransitionLayout, - animatedContentScope = this@composable + } ) } diff --git a/app/src/main/java/com/fastmask/ui/navigation/NavRoutes.kt b/app/src/main/java/com/fastmask/ui/navigation/NavRoutes.kt index 74053ad..1bb7d41 100644 --- a/app/src/main/java/com/fastmask/ui/navigation/NavRoutes.kt +++ b/app/src/main/java/com/fastmask/ui/navigation/NavRoutes.kt @@ -5,6 +5,7 @@ object NavRoutes { const val EMAIL_LIST = "email_list" const val CREATE_EMAIL = "create_email" const val EMAIL_DETAIL = "email_detail/{emailId}" + const val SETTINGS = "settings" fun emailDetail(emailId: String) = "email_detail/$emailId" } diff --git a/app/src/main/java/com/fastmask/ui/settings/SettingsScreen.kt b/app/src/main/java/com/fastmask/ui/settings/SettingsScreen.kt new file mode 100644 index 0000000..71ddbe6 --- /dev/null +++ b/app/src/main/java/com/fastmask/ui/settings/SettingsScreen.kt @@ -0,0 +1,304 @@ +package com.fastmask.ui.settings + +import android.content.Intent +import android.net.Uri +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.selection.selectableGroup +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.Logout +import androidx.compose.material.icons.filled.ChevronRight +import androidx.compose.material.icons.filled.Email +import androidx.compose.material.icons.filled.Language +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.hapticfeedback.HapticFeedbackType +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.fastmask.BuildConfig +import com.fastmask.R +import com.fastmask.domain.model.Language +import kotlinx.coroutines.flow.collectLatest + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SettingsScreen( + onNavigateBack: () -> Unit, + onLogout: () -> Unit, + viewModel: SettingsViewModel = hiltViewModel() +) { + val uiState by viewModel.uiState.collectAsState() + val context = LocalContext.current + val haptic = LocalHapticFeedback.current + var showLanguageDialog by remember { mutableStateOf(false) } + + LaunchedEffect(Unit) { + viewModel.events.collectLatest { event -> + when (event) { + is SettingsEvent.LoggedOut -> onLogout() + } + } + } + + if (showLanguageDialog) { + LanguagePickerDialog( + selectedLanguage = uiState.selectedLanguage, + onLanguageSelected = { language -> + viewModel.onLanguageSelected(language) + showLanguageDialog = false + }, + onDismiss = { showLanguageDialog = false } + ) + } + + Scaffold( + topBar = { + TopAppBar( + title = { Text(stringResource(R.string.settings_title)) }, + navigationIcon = { + IconButton( + onClick = { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + onNavigateBack() + }, + modifier = Modifier.semantics { + contentDescription = "Navigate back" + } + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = null + ) + } + }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + titleContentColor = MaterialTheme.colorScheme.onSurface + ) + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .verticalScroll(rememberScrollState()) + ) { + // Language Setting + ListItem( + headlineContent = { Text(stringResource(R.string.settings_language)) }, + supportingContent = { + val languageName = uiState.selectedLanguage?.let { + stringResource(it.displayNameRes) + } ?: stringResource(R.string.settings_system_default) + Text(languageName) + }, + leadingContent = { + Icon( + imageVector = Icons.Default.Language, + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + }, + trailingContent = { + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = null + ) + }, + modifier = Modifier.clickable { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + showLanguageDialog = true + } + ) + + HorizontalDivider() + + // Contact & Feedback + ListItem( + headlineContent = { Text(stringResource(R.string.settings_contact)) }, + supportingContent = { Text(stringResource(R.string.settings_contact_description)) }, + leadingContent = { + Icon( + imageVector = Icons.Default.Email, + contentDescription = null, + modifier = Modifier.size(24.dp) + ) + }, + trailingContent = { + Icon( + imageVector = Icons.Default.ChevronRight, + contentDescription = null + ) + }, + modifier = Modifier.clickable { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + val emailIntent = Intent(Intent.ACTION_SENDTO).apply { + data = Uri.parse("mailto:") + putExtra(Intent.EXTRA_EMAIL, arrayOf("pawel@orzech.me")) + putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.settings_feedback_subject)) + } + if (emailIntent.resolveActivity(context.packageManager) != null) { + context.startActivity(emailIntent) + } + } + ) + + HorizontalDivider() + + // Logout + ListItem( + headlineContent = { Text(stringResource(R.string.settings_logout)) }, + supportingContent = { Text(stringResource(R.string.settings_logout_description)) }, + leadingContent = { + Icon( + imageVector = Icons.AutoMirrored.Filled.Logout, + contentDescription = null, + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.error + ) + }, + modifier = Modifier.clickable { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + viewModel.logout() + } + ) + + Spacer(modifier = Modifier.weight(1f)) + + // Version + Box( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = stringResource(R.string.settings_version, BuildConfig.VERSION_NAME), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } + } +} + +@Composable +private fun LanguagePickerDialog( + selectedLanguage: Language?, + onLanguageSelected: (Language?) -> Unit, + onDismiss: () -> Unit +) { + val haptic = LocalHapticFeedback.current + + AlertDialog( + onDismissRequest = onDismiss, + title = { Text(stringResource(R.string.settings_select_language)) }, + text = { + LazyColumn( + modifier = Modifier.selectableGroup() + ) { + // System Default option + item { + Row( + modifier = Modifier + .fillMaxWidth() + .selectable( + selected = selectedLanguage == null, + onClick = { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + onLanguageSelected(null) + }, + role = Role.RadioButton + ) + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = selectedLanguage == null, + onClick = null + ) + Text( + text = stringResource(R.string.settings_system_default), + modifier = Modifier.padding(start = 16.dp), + style = MaterialTheme.typography.bodyLarge + ) + } + } + + items(Language.entries) { language -> + Row( + modifier = Modifier + .fillMaxWidth() + .selectable( + selected = selectedLanguage == language, + onClick = { + haptic.performHapticFeedback(HapticFeedbackType.LongPress) + onLanguageSelected(language) + }, + role = Role.RadioButton + ) + .padding(vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = selectedLanguage == language, + onClick = null + ) + Text( + text = stringResource(language.displayNameRes), + modifier = Modifier.padding(start = 16.dp), + style = MaterialTheme.typography.bodyLarge + ) + } + } + } + }, + confirmButton = { + TextButton(onClick = onDismiss) { + Text(stringResource(R.string.email_detail_delete_cancel)) + } + } + ) +} diff --git a/app/src/main/java/com/fastmask/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/fastmask/ui/settings/SettingsViewModel.kt new file mode 100644 index 0000000..6f64b0b --- /dev/null +++ b/app/src/main/java/com/fastmask/ui/settings/SettingsViewModel.kt @@ -0,0 +1,66 @@ +package com.fastmask.ui.settings + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.fastmask.domain.model.Language +import com.fastmask.domain.usecase.GetCurrentLanguageUseCase +import com.fastmask.domain.usecase.LogoutUseCase +import com.fastmask.domain.usecase.SetLanguageUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SettingsViewModel @Inject constructor( + private val getCurrentLanguageUseCase: GetCurrentLanguageUseCase, + private val setLanguageUseCase: SetLanguageUseCase, + private val logoutUseCase: LogoutUseCase +) : ViewModel() { + + private val _uiState = MutableStateFlow(SettingsUiState()) + val uiState: StateFlow = _uiState.asStateFlow() + + private val _events = MutableSharedFlow() + val events: SharedFlow = _events.asSharedFlow() + + init { + loadCurrentLanguage() + } + + private fun loadCurrentLanguage() { + viewModelScope.launch { + getCurrentLanguageUseCase().collect { language -> + _uiState.update { it.copy(selectedLanguage = language) } + } + } + } + + fun onLanguageSelected(language: Language?) { + viewModelScope.launch { + setLanguageUseCase(language) + _uiState.update { it.copy(selectedLanguage = language) } + } + } + + fun logout() { + logoutUseCase() + viewModelScope.launch { + _events.emit(SettingsEvent.LoggedOut) + } + } +} + +data class SettingsUiState( + val selectedLanguage: Language? = null +) + +sealed class SettingsEvent { + data object LoggedOut : SettingsEvent() +} diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000..3727319 --- /dev/null +++ b/app/src/main/res/values-ar/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + مدير البريد المقنّع من Fastmail + رمز API + أدخل رمز API الخاص بـ Fastmail + تسجيل الدخول + إظهار الرمز + إخفاء الرمز + كيفية الحصول على رمز API: + ١. سجّل الدخول إلى تطبيق Fastmail + ٢. انتقل إلى الإعدادات > الخصوصية والأمان + ٣. انقر على التكاملات > رموز API + ٤. أنشئ رمزاً جديداً بصلاحية \"Masked Email\" + ٥. انسخ الرمز والصقه أعلاه + ١. سجّل الدخول إلى تطبيق Fastmail\n٢. انتقل إلى الإعدادات > الخصوصية والأمان\n٣. انقر على التكاملات > رموز API\n٤. أنشئ رمزاً جديداً بصلاحية \"Masked Email\"\n٥. انسخ الرمز والصقه أعلاه + + + البريد المقنّع + البحث في البريد… + مسح البحث + تصفية البريد + إنشاء + إنشاء بريد مقنّع جديد + لم يتم العثور على بريد مقنّع + تسجيل الخروج + الإعدادات + الكل + مُفعّل + معطّل + محذوف + + + إنشاء بريد مقنّع + بادئة البريد (اختياري) + مثال: mysite_shopping + الحد الأقصى ٦٤ حرفاً: أحرف صغيرة، أرقام، شرطات سفلية + النطاق المرتبط (اختياري) + مثال: example.com + الوصف (اختياري) + مثال: حساب التسوق + الرابط (اختياري) + مثال: https://example.com + الحالة الأولية + إنشاء بريد مقنّع + تم الإنشاء: %s + نسخ + رجوع + + + تفاصيل البريد + نسخ عنوان البريد + حذف البريد + الحالة: + أنشأه: %s + تاريخ الإنشاء: %s + آخر رسالة: %s + تفعيل + تعطيل + تعديل التفاصيل + الوصف + النطاق المرتبط + الرابط + حفظ التغييرات + تم التحديث بنجاح + تم الحذف بنجاح + تم النسخ إلى الحافظة + حذف البريد المقنّع + هل أنت متأكد أنك تريد حذف هذا البريد المقنّع؟ لا يمكن التراجع عن هذا الإجراء. + حذف + إلغاء + + + مُفعّل + معطّل + محذوف + قيد الانتظار + + + فشل تحميل البريد + إعادة المحاولة + حدث خطأ + + + الإعدادات + اللغة + اختر لغة التطبيق + الافتراضي للنظام + التواصل والملاحظات + أرسل لنا ملاحظاتك + تسجيل الخروج + الخروج من حسابك + الإصدار %s + اختر اللغة + ملاحظات FastMask + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml new file mode 100644 index 0000000..5e215aa --- /dev/null +++ b/app/src/main/res/values-bn/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Fastmail মাস্কড ইমেইল ম্যানেজার + API টোকেন + আপনার Fastmail API টোকেন দিন + লগইন + টোকেন দেখান + টোকেন লুকান + কীভাবে আপনার API টোকেন পাবেন: + ১. Fastmail ওয়েব অ্যাপে লগইন করুন + ২. সেটিংস > প্রাইভেসি এবং সিকিউরিটিতে যান + ৩. ইন্টিগ্রেশন > API টোকেনে ক্লিক করুন + ৪. \"Masked Email\" স্কোপ সহ একটি নতুন টোকেন তৈরি করুন + ৫. টোকেন কপি করুন এবং উপরে পেস্ট করুন + ১. Fastmail ওয়েব অ্যাপে লগইন করুন\n২. সেটিংস > প্রাইভেসি এবং সিকিউরিটিতে যান\n৩. ইন্টিগ্রেশন > API টোকেনে ক্লিক করুন\n৪. \"Masked Email\" স্কোপ সহ একটি নতুন টোকেন তৈরি করুন\n৫. টোকেন কপি করুন এবং উপরে পেস্ট করুন + + + মাস্কড ইমেইল + ইমেইল খুঁজুন… + সার্চ মুছুন + ইমেইল ফিল্টার করুন + তৈরি করুন + নতুন মাস্কড ইমেইল তৈরি করুন + কোনো মাস্কড ইমেইল পাওয়া যায়নি + লগআউট + সেটিংস + সব + সক্রিয় + নিষ্ক্রিয় + মুছে ফেলা + + + মাস্কড ইমেইল তৈরি করুন + ইমেইল প্রিফিক্স (ঐচ্ছিক) + যেমন, mysite_shopping + সর্বোচ্চ ৬৪ অক্ষর: ছোট হাতের অক্ষর, সংখ্যা, আন্ডারস্কোর + সংশ্লিষ্ট ডোমেইন (ঐচ্ছিক) + যেমন, example.com + বিবরণ (ঐচ্ছিক) + যেমন, শপিং অ্যাকাউন্ট + URL (ঐচ্ছিক) + যেমন, https://example.com + প্রাথমিক অবস্থা + মাস্কড ইমেইল তৈরি করুন + তৈরি হয়েছে: %s + কপি + পিছনে যান + + + ইমেইল বিবরণ + ইমেইল ঠিকানা কপি করুন + ইমেইল মুছুন + অবস্থা: + তৈরি করেছেন: %s + তৈরি: %s + শেষ বার্তা: %s + সক্রিয় করুন + নিষ্ক্রিয় করুন + বিবরণ সম্পাদনা করুন + বিবরণ + সংশ্লিষ্ট ডোমেইন + URL + পরিবর্তন সংরক্ষণ করুন + সফলভাবে আপডেট হয়েছে + সফলভাবে মুছে ফেলা হয়েছে + ক্লিপবোর্ডে কপি হয়েছে + মাস্কড ইমেইল মুছুন + আপনি কি নিশ্চিত যে এই মাস্কড ইমেইলটি মুছতে চান? এই কাজটি পূর্বাবস্থায় ফেরানো যাবে না। + মুছুন + বাতিল + + + সক্রিয় + নিষ্ক্রিয় + মুছে ফেলা + অপেক্ষমাণ + + + ইমেইল লোড করতে ব্যর্থ + পুনরায় চেষ্টা করুন + একটি ত্রুটি ঘটেছে + + + সেটিংস + ভাষা + অ্যাপের ভাষা নির্বাচন করুন + সিস্টেম ডিফল্ট + যোগাযোগ এবং প্রতিক্রিয়া + আমাদের আপনার প্রতিক্রিয়া পাঠান + লগআউট + আপনার অ্যাকাউন্ট থেকে সাইন আউট করুন + সংস্করণ %s + ভাষা নির্বাচন করুন + FastMask প্রতিক্রিয়া + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..7c5a864 --- /dev/null +++ b/app/src/main/res/values-de/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Fastmail Maskierte-E-Mail-Manager + API-Token + Geben Sie Ihr Fastmail API-Token ein + Anmelden + Token anzeigen + Token ausblenden + So erhalten Sie Ihr API-Token: + 1. Melden Sie sich bei der Fastmail Web-App an + 2. Gehen Sie zu Einstellungen > Datenschutz und Sicherheit + 3. Klicken Sie auf Integrationen > API-Token + 4. Erstellen Sie ein neues Token mit dem Bereich \"Masked Email\" + 5. Kopieren Sie das Token und fügen Sie es oben ein + 1. Melden Sie sich bei der Fastmail Web-App an\n2. Gehen Sie zu Einstellungen > Datenschutz und Sicherheit\n3. Klicken Sie auf Integrationen > API-Token\n4. Erstellen Sie ein neues Token mit dem Bereich \"Masked Email\"\n5. Kopieren Sie das Token und fügen Sie es oben ein + + + Maskierte E-Mails + E-Mails suchen… + Suche löschen + E-Mails filtern + Erstellen + Neue maskierte E-Mail erstellen + Keine maskierten E-Mails gefunden + Abmelden + Einstellungen + Alle + Aktiviert + Deaktiviert + Gelöscht + + + Maskierte E-Mail erstellen + E-Mail-Präfix (optional) + z.B. meinseite_einkauf + Max. 64 Zeichen: Kleinbuchstaben, Zahlen, Unterstriche + Zugehörige Domain (optional) + z.B. beispiel.de + Beschreibung (optional) + z.B. Einkaufskonto + URL (optional) + z.B. https://beispiel.de + Anfangszustand + Maskierte E-Mail erstellen + Erstellt: %s + Kopieren + Zurück + + + E-Mail-Details + E-Mail-Adresse kopieren + E-Mail löschen + Status: + Erstellt von: %s + Erstellt: %s + Letzte Nachricht: %s + Aktivieren + Deaktivieren + Details bearbeiten + Beschreibung + Zugehörige Domain + URL + Änderungen speichern + Erfolgreich aktualisiert + Erfolgreich gelöscht + In Zwischenablage kopiert + Maskierte E-Mail löschen + Sind Sie sicher, dass Sie diese maskierte E-Mail löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden. + Löschen + Abbrechen + + + Aktiviert + Deaktiviert + Gelöscht + Ausstehend + + + E-Mails konnten nicht geladen werden + Erneut versuchen + Ein Fehler ist aufgetreten + + + Einstellungen + Sprache + App-Sprache auswählen + Systemstandard + Kontakt und Feedback + Senden Sie uns Ihr Feedback + Abmelden + Von Ihrem Konto abmelden + Version %s + Sprache auswählen + FastMask Feedback + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml new file mode 100644 index 0000000..3f519f1 --- /dev/null +++ b/app/src/main/res/values-es/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Gestor de correos enmascarados de Fastmail + Token de API + Introduce tu token de API de Fastmail + Iniciar sesión + Mostrar token + Ocultar token + Cómo obtener tu token de API: + 1. Inicia sesión en la aplicación web de Fastmail + 2. Ve a Configuración > Privacidad y Seguridad + 3. Haz clic en Integraciones > Tokens de API + 4. Crea un nuevo token con el alcance \"Masked Email\" + 5. Copia el token y pégalo arriba + 1. Inicia sesión en la aplicación web de Fastmail\n2. Ve a Configuración > Privacidad y Seguridad\n3. Haz clic en Integraciones > Tokens de API\n4. Crea un nuevo token con el alcance \"Masked Email\"\n5. Copia el token y pégalo arriba + + + Correos enmascarados + Buscar correos… + Borrar búsqueda + Filtrar correos + Crear + Crear nuevo correo enmascarado + No se encontraron correos enmascarados + Cerrar sesión + Configuración + Todos + Habilitados + Deshabilitados + Eliminados + + + Crear correo enmascarado + Prefijo de correo (opcional) + ej., misitio_compras + Máx 64 caracteres: letras minúsculas, números, guiones bajos + Dominio asociado (opcional) + ej., ejemplo.com + Descripción (opcional) + ej., Cuenta de compras + URL (opcional) + ej., https://ejemplo.com + Estado inicial + Crear correo enmascarado + Creado: %s + Copiar + Volver + + + Detalles del correo + Copiar dirección de correo + Eliminar correo + Estado: + Creado por: %s + Creado: %s + Último mensaje: %s + Habilitar + Deshabilitar + Editar detalles + Descripción + Dominio asociado + URL + Guardar cambios + Actualizado correctamente + Eliminado correctamente + Copiado al portapapeles + Eliminar correo enmascarado + ¿Estás seguro de que quieres eliminar este correo enmascarado? Esta acción no se puede deshacer. + Eliminar + Cancelar + + + Habilitado + Deshabilitado + Eliminado + Pendiente + + + Error al cargar los correos + Reintentar + Se produjo un error + + + Configuración + Idioma + Seleccionar idioma de la app + Predeterminado del sistema + Contacto y comentarios + Envíanos tus comentarios + Cerrar sesión + Salir de tu cuenta + Versión %s + Seleccionar idioma + Comentarios de FastMask + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml new file mode 100644 index 0000000..edefd41 --- /dev/null +++ b/app/src/main/res/values-fr/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Gestionnaire d\'emails masqués Fastmail + Jeton API + Entrez votre jeton API Fastmail + Connexion + Afficher le jeton + Masquer le jeton + Comment obtenir votre jeton API : + 1. Connectez-vous à l\'application web Fastmail + 2. Allez dans Paramètres > Confidentialité et sécurité + 3. Cliquez sur Intégrations > Jetons API + 4. Créez un nouveau jeton avec la portée \"Masked Email\" + 5. Copiez le jeton et collez-le ci-dessus + 1. Connectez-vous à l\'application web Fastmail\n2. Allez dans Paramètres > Confidentialité et sécurité\n3. Cliquez sur Intégrations > Jetons API\n4. Créez un nouveau jeton avec la portée \"Masked Email\"\n5. Copiez le jeton et collez-le ci-dessus + + + Emails masqués + Rechercher des emails… + Effacer la recherche + Filtrer les emails + Créer + Créer un nouvel email masqué + Aucun email masqué trouvé + Déconnexion + Paramètres + Tous + Activés + Désactivés + Supprimés + + + Créer un email masqué + Préfixe d\'email (optionnel) + ex. : monsite_achats + Max 64 caractères : lettres minuscules, chiffres, tirets bas + Domaine associé (optionnel) + ex. : exemple.com + Description (optionnel) + ex. : Compte d\'achats + URL (optionnel) + ex. : https://exemple.com + État initial + Créer l\'email masqué + Créé : %s + Copier + Retour + + + Détails de l\'email + Copier l\'adresse email + Supprimer l\'email + Statut : + Créé par : %s + Créé : %s + Dernier message : %s + Activer + Désactiver + Modifier les détails + Description + Domaine associé + URL + Enregistrer les modifications + Mis à jour avec succès + Supprimé avec succès + Copié dans le presse-papiers + Supprimer l\'email masqué + Êtes-vous sûr de vouloir supprimer cet email masqué ? Cette action est irréversible. + Supprimer + Annuler + + + Activé + Désactivé + Supprimé + En attente + + + Échec du chargement des emails + Réessayer + Une erreur s\'est produite + + + Paramètres + Langue + Sélectionner la langue de l\'app + Par défaut du système + Contact et commentaires + Envoyez-nous vos commentaires + Déconnexion + Se déconnecter de votre compte + Version %s + Sélectionner la langue + Commentaires FastMask + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml new file mode 100644 index 0000000..e9a3f3a --- /dev/null +++ b/app/src/main/res/values-hi/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Fastmail मास्क्ड ईमेल प्रबंधक + API टोकन + अपना Fastmail API टोकन दर्ज करें + लॉग इन करें + टोकन दिखाएं + टोकन छुपाएं + अपना API टोकन कैसे प्राप्त करें: + 1. Fastmail वेब ऐप में लॉग इन करें + 2. सेटिंग्स > गोपनीयता और सुरक्षा पर जाएं + 3. इंटीग्रेशन > API टोकन पर क्लिक करें + 4. \"Masked Email\" स्कोप के साथ एक नया टोकन बनाएं + 5. टोकन कॉपी करें और ऊपर पेस्ट करें + 1. Fastmail वेब ऐप में लॉग इन करें\n2. सेटिंग्स > गोपनीयता और सुरक्षा पर जाएं\n3. इंटीग्रेशन > API टोकन पर क्लिक करें\n4. \"Masked Email\" स्कोप के साथ एक नया टोकन बनाएं\n5. टोकन कॉपी करें और ऊपर पेस्ट करें + + + मास्क्ड ईमेल + ईमेल खोजें… + खोज साफ़ करें + ईमेल फ़िल्टर करें + बनाएं + नया मास्क्ड ईमेल बनाएं + कोई मास्क्ड ईमेल नहीं मिला + लॉग आउट + सेटिंग्स + सभी + सक्षम + अक्षम + हटाए गए + + + मास्क्ड ईमेल बनाएं + ईमेल प्रीफ़िक्स (वैकल्पिक) + जैसे, mysite_shopping + अधिकतम 64 अक्षर: छोटे अक्षर, संख्याएं, अंडरस्कोर + संबंधित डोमेन (वैकल्पिक) + जैसे, example.com + विवरण (वैकल्पिक) + जैसे, शॉपिंग अकाउंट + URL (वैकल्पिक) + जैसे, https://example.com + प्रारंभिक स्थिति + मास्क्ड ईमेल बनाएं + बनाया गया: %s + कॉपी करें + वापस जाएं + + + ईमेल विवरण + ईमेल पता कॉपी करें + ईमेल हटाएं + स्थिति: + बनाने वाला: %s + बनाया गया: %s + अंतिम संदेश: %s + सक्षम करें + अक्षम करें + विवरण संपादित करें + विवरण + संबंधित डोमेन + URL + परिवर्तन सहेजें + सफलतापूर्वक अपडेट किया गया + सफलतापूर्वक हटाया गया + क्लिपबोर्ड पर कॉपी किया गया + मास्क्ड ईमेल हटाएं + क्या आप वाकई इस मास्क्ड ईमेल को हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती। + हटाएं + रद्द करें + + + सक्षम + अक्षम + हटाया गया + लंबित + + + ईमेल लोड करने में विफल + पुनः प्रयास करें + एक त्रुटि हुई + + + सेटिंग्स + भाषा + ऐप की भाषा चुनें + सिस्टम डिफ़ॉल्ट + संपर्क और फ़ीडबैक + हमें अपना फ़ीडबैक भेजें + लॉग आउट + अपने खाते से साइन आउट करें + संस्करण %s + भाषा चुनें + FastMask फ़ीडबैक + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml new file mode 100644 index 0000000..828414a --- /dev/null +++ b/app/src/main/res/values-id/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Pengelola Email Tersamar Fastmail + Token API + Masukkan token API Fastmail Anda + Masuk + Tampilkan token + Sembunyikan token + Cara mendapatkan token API: + 1. Masuk ke aplikasi web Fastmail + 2. Buka Pengaturan > Privasi & Keamanan + 3. Klik Integrasi > Token API + 4. Buat token baru dengan cakupan \"Masked Email\" + 5. Salin token dan tempel di atas + 1. Masuk ke aplikasi web Fastmail\n2. Buka Pengaturan > Privasi & Keamanan\n3. Klik Integrasi > Token API\n4. Buat token baru dengan cakupan \"Masked Email\"\n5. Salin token dan tempel di atas + + + Email Tersamar + Cari email… + Hapus pencarian + Filter email + Buat + Buat email tersamar baru + Tidak ada email tersamar ditemukan + Keluar + Pengaturan + Semua + Aktif + Nonaktif + Dihapus + + + Buat Email Tersamar + Awalan email (opsional) + mis., situsku_belanja + Maks 64 karakter: huruf kecil, angka, garis bawah + Domain terkait (opsional) + mis., contoh.com + Deskripsi (opsional) + mis., Akun belanja + URL (opsional) + mis., https://contoh.com + Status awal + Buat Email Tersamar + Dibuat: %s + Salin + Kembali + + + Detail Email + Salin alamat email + Hapus email + Status: + Dibuat oleh: %s + Dibuat: %s + Pesan terakhir: %s + Aktifkan + Nonaktifkan + Edit Detail + Deskripsi + Domain terkait + URL + Simpan Perubahan + Berhasil diperbarui + Berhasil dihapus + Disalin ke papan klip + Hapus Email Tersamar + Apakah Anda yakin ingin menghapus email tersamar ini? Tindakan ini tidak dapat dibatalkan. + Hapus + Batal + + + Aktif + Nonaktif + Dihapus + Tertunda + + + Gagal memuat email + Coba Lagi + Terjadi kesalahan + + + Pengaturan + Bahasa + Pilih bahasa aplikasi + Default Sistem + Kontak & Masukan + Kirimkan masukan Anda + Keluar + Keluar dari akun Anda + Versi %s + Pilih Bahasa + Masukan FastMask + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml new file mode 100644 index 0000000..f25d5af --- /dev/null +++ b/app/src/main/res/values-it/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Gestore email mascherate di Fastmail + Token API + Inserisci il tuo token API Fastmail + Accedi + Mostra token + Nascondi token + Come ottenere il tuo token API: + 1. Accedi all\'app web di Fastmail + 2. Vai su Impostazioni > Privacy e Sicurezza + 3. Clicca su Integrazioni > Token API + 4. Crea un nuovo token con ambito \"Masked Email\" + 5. Copia il token e incollalo sopra + 1. Accedi all\'app web di Fastmail\n2. Vai su Impostazioni > Privacy e Sicurezza\n3. Clicca su Integrazioni > Token API\n4. Crea un nuovo token con ambito \"Masked Email\"\n5. Copia il token e incollalo sopra + + + Email mascherate + Cerca email… + Cancella ricerca + Filtra email + Crea + Crea nuova email mascherata + Nessuna email mascherata trovata + Esci + Impostazioni + Tutte + Attive + Disattivate + Eliminate + + + Crea email mascherata + Prefisso email (opzionale) + es. miosito_acquisti + Max 64 caratteri: lettere minuscole, numeri, trattini bassi + Dominio associato (opzionale) + es. esempio.it + Descrizione (opzionale) + es. Account acquisti + URL (opzionale) + es. https://esempio.it + Stato iniziale + Crea email mascherata + Creata: %s + Copia + Indietro + + + Dettagli email + Copia indirizzo email + Elimina email + Stato: + Creata da: %s + Creata: %s + Ultimo messaggio: %s + Attiva + Disattiva + Modifica dettagli + Descrizione + Dominio associato + URL + Salva modifiche + Aggiornato con successo + Eliminato con successo + Copiato negli appunti + Elimina email mascherata + Sei sicuro di voler eliminare questa email mascherata? Questa azione non può essere annullata. + Elimina + Annulla + + + Attiva + Disattivata + Eliminata + In attesa + + + Impossibile caricare le email + Riprova + Si è verificato un errore + + + Impostazioni + Lingua + Seleziona la lingua dell\'app + Predefinito di sistema + Contatto e feedback + Inviaci il tuo feedback + Esci + Disconnettiti dal tuo account + Versione %s + Seleziona lingua + Feedback FastMask + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml new file mode 100644 index 0000000..8a67e8f --- /dev/null +++ b/app/src/main/res/values-ja/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Fastmail マスクメール管理 + APIトークン + Fastmail APIトークンを入力 + ログイン + トークンを表示 + トークンを非表示 + APIトークンの取得方法: + 1. Fastmail Webアプリにログイン + 2. 設定 > プライバシーとセキュリティに移動 + 3. 連携 > APIトークンをクリック + 4. 「Masked Email」スコープで新しいトークンを作成 + 5. トークンをコピーして上に貼り付け + 1. Fastmail Webアプリにログイン\n2. 設定 > プライバシーとセキュリティに移動\n3. 連携 > APIトークンをクリック\n4. 「Masked Email」スコープで新しいトークンを作成\n5. トークンをコピーして上に貼り付け + + + マスクメール + メールを検索… + 検索をクリア + メールをフィルター + 作成 + 新しいマスクメールを作成 + マスクメールが見つかりません + ログアウト + 設定 + すべて + 有効 + 無効 + 削除済み + + + マスクメールを作成 + メールプレフィックス(任意) + 例:mysite_shopping + 最大64文字:小文字、数字、アンダースコア + 関連ドメイン(任意) + 例:example.com + 説明(任意) + 例:ショッピングアカウント + URL(任意) + 例:https://example.com + 初期状態 + マスクメールを作成 + 作成完了:%s + コピー + 戻る + + + メール詳細 + メールアドレスをコピー + メールを削除 + 状態: + 作成者:%s + 作成日:%s + 最後のメッセージ:%s + 有効にする + 無効にする + 詳細を編集 + 説明 + 関連ドメイン + URL + 変更を保存 + 更新しました + 削除しました + クリップボードにコピーしました + マスクメールを削除 + このマスクメールを削除してもよろしいですか?この操作は元に戻せません。 + 削除 + キャンセル + + + 有効 + 無効 + 削除済み + 保留中 + + + メールの読み込みに失敗しました + 再試行 + エラーが発生しました + + + 設定 + 言語 + アプリの言語を選択 + システムの既定 + お問い合わせとフィードバック + フィードバックを送信 + ログアウト + アカウントからサインアウト + バージョン %s + 言語を選択 + FastMask フィードバック + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml new file mode 100644 index 0000000..875f768 --- /dev/null +++ b/app/src/main/res/values-ko/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Fastmail 마스크 이메일 관리자 + API 토큰 + Fastmail API 토큰을 입력하세요 + 로그인 + 토큰 표시 + 토큰 숨기기 + API 토큰 얻는 방법: + 1. Fastmail 웹 앱에 로그인 + 2. 설정 > 개인정보 및 보안으로 이동 + 3. 통합 > API 토큰 클릭 + 4. \"Masked Email\" 범위로 새 토큰 생성 + 5. 토큰을 복사하여 위에 붙여넣기 + 1. Fastmail 웹 앱에 로그인\n2. 설정 > 개인정보 및 보안으로 이동\n3. 통합 > API 토큰 클릭\n4. \"Masked Email\" 범위로 새 토큰 생성\n5. 토큰을 복사하여 위에 붙여넣기 + + + 마스크 이메일 + 이메일 검색… + 검색 지우기 + 이메일 필터 + 생성 + 새 마스크 이메일 생성 + 마스크 이메일을 찾을 수 없습니다 + 로그아웃 + 설정 + 전체 + 활성화됨 + 비활성화됨 + 삭제됨 + + + 마스크 이메일 생성 + 이메일 접두사 (선택) + 예: mysite_shopping + 최대 64자: 소문자, 숫자, 밑줄 + 연결된 도메인 (선택) + 예: example.com + 설명 (선택) + 예: 쇼핑 계정 + URL (선택) + 예: https://example.com + 초기 상태 + 마스크 이메일 생성 + 생성됨: %s + 복사 + 뒤로 + + + 이메일 상세 + 이메일 주소 복사 + 이메일 삭제 + 상태: + 생성자: %s + 생성일: %s + 마지막 메시지: %s + 활성화 + 비활성화 + 상세 정보 편집 + 설명 + 연결된 도메인 + URL + 변경 사항 저장 + 업데이트 완료 + 삭제 완료 + 클립보드에 복사됨 + 마스크 이메일 삭제 + 이 마스크 이메일을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다. + 삭제 + 취소 + + + 활성화됨 + 비활성화됨 + 삭제됨 + 대기 중 + + + 이메일 로드 실패 + 다시 시도 + 오류가 발생했습니다 + + + 설정 + 언어 + 앱 언어 선택 + 시스템 기본값 + 연락처 및 피드백 + 피드백을 보내주세요 + 로그아웃 + 계정에서 로그아웃 + 버전 %s + 언어 선택 + FastMask 피드백 + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml new file mode 100644 index 0000000..f36ab95 --- /dev/null +++ b/app/src/main/res/values-nl/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Fastmail Gemaskeerde E-mail Beheerder + API-token + Voer uw Fastmail API-token in + Inloggen + Token tonen + Token verbergen + Hoe u uw API-token krijgt: + 1. Log in op de Fastmail web-app + 2. Ga naar Instellingen > Privacy en Beveiliging + 3. Klik op Integraties > API-tokens + 4. Maak een nieuw token met \"Masked Email\" scope + 5. Kopieer het token en plak het hierboven + 1. Log in op de Fastmail web-app\n2. Ga naar Instellingen > Privacy en Beveiliging\n3. Klik op Integraties > API-tokens\n4. Maak een nieuw token met \"Masked Email\" scope\n5. Kopieer het token en plak het hierboven + + + Gemaskeerde e-mails + E-mails zoeken… + Zoekopdracht wissen + E-mails filteren + Aanmaken + Nieuwe gemaskeerde e-mail aanmaken + Geen gemaskeerde e-mails gevonden + Uitloggen + Instellingen + Alle + Ingeschakeld + Uitgeschakeld + Verwijderd + + + Gemaskeerde e-mail aanmaken + E-mail prefix (optioneel) + bijv. mijnsite_winkelen + Max 64 tekens: kleine letters, cijfers, underscores + Gekoppeld domein (optioneel) + bijv. voorbeeld.nl + Beschrijving (optioneel) + bijv. Winkelaccount + URL (optioneel) + bijv. https://voorbeeld.nl + Beginstatus + Gemaskeerde e-mail aanmaken + Aangemaakt: %s + Kopiëren + Terug + + + E-maildetails + E-mailadres kopiëren + E-mail verwijderen + Status: + Aangemaakt door: %s + Aangemaakt: %s + Laatste bericht: %s + Inschakelen + Uitschakelen + Details bewerken + Beschrijving + Gekoppeld domein + URL + Wijzigingen opslaan + Succesvol bijgewerkt + Succesvol verwijderd + Gekopieerd naar klembord + Gemaskeerde e-mail verwijderen + Weet u zeker dat u deze gemaskeerde e-mail wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt. + Verwijderen + Annuleren + + + Ingeschakeld + Uitgeschakeld + Verwijderd + In afwachting + + + Kon e-mails niet laden + Opnieuw proberen + Er is een fout opgetreden + + + Instellingen + Taal + App-taal selecteren + Systeemstandaard + Contact en feedback + Stuur ons uw feedback + Uitloggen + Afmelden bij uw account + Versie %s + Taal selecteren + FastMask Feedback + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml new file mode 100644 index 0000000..a29b5e7 --- /dev/null +++ b/app/src/main/res/values-pl/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Menedżer ukrytych adresów Fastmail + Token API + Wprowadź token API Fastmail + Zaloguj się + Pokaż token + Ukryj token + Jak uzyskać token API: + 1. Zaloguj się do aplikacji webowej Fastmail + 2. Przejdź do Ustawienia > Prywatność i bezpieczeństwo + 3. Kliknij Integracje > Tokeny API + 4. Utwórz nowy token z zakresem \"Masked Email\" + 5. Skopiuj token i wklej go powyżej + 1. Zaloguj się do aplikacji webowej Fastmail\n2. Przejdź do Ustawienia > Prywatność i bezpieczeństwo\n3. Kliknij Integracje > Tokeny API\n4. Utwórz nowy token z zakresem \"Masked Email\"\n5. Skopiuj token i wklej go powyżej + + + Ukryte adresy + Szukaj adresów… + Wyczyść wyszukiwanie + Filtruj adresy + Utwórz + Utwórz nowy ukryty adres + Nie znaleziono ukrytych adresów + Wyloguj + Ustawienia + Wszystkie + Aktywne + Wyłączone + Usunięte + + + Utwórz ukryty adres + Prefiks adresu (opcjonalnie) + np. mojastrona_zakupy + Maks. 64 znaki: małe litery, cyfry, podkreślenia + Powiązana domena (opcjonalnie) + np. przyklad.pl + Opis (opcjonalnie) + np. Konto zakupowe + URL (opcjonalnie) + np. https://przyklad.pl + Stan początkowy + Utwórz ukryty adres + Utworzono: %s + Kopiuj + Wstecz + + + Szczegóły adresu + Kopiuj adres e-mail + Usuń adres + Status: + Utworzony przez: %s + Utworzono: %s + Ostatnia wiadomość: %s + Włącz + Wyłącz + Edytuj szczegóły + Opis + Powiązana domena + URL + Zapisz zmiany + Zaktualizowano pomyślnie + Usunięto pomyślnie + Skopiowano do schowka + Usuń ukryty adres + Czy na pewno chcesz usunąć ten ukryty adres? Tej operacji nie można cofnąć. + Usuń + Anuluj + + + Aktywny + Wyłączony + Usunięty + Oczekujący + + + Nie udało się załadować adresów + Ponów próbę + Wystąpił błąd + + + Ustawienia + Język + Wybierz język aplikacji + Domyślny systemowy + Kontakt i opinie + Wyślij nam swoją opinię + Wyloguj + Wyloguj się z konta + Wersja %s + Wybierz język + Opinia o FastMask + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml new file mode 100644 index 0000000..4f4f58d --- /dev/null +++ b/app/src/main/res/values-pt/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Gerenciador de emails mascarados do Fastmail + Token de API + Digite seu token de API do Fastmail + Entrar + Mostrar token + Ocultar token + Como obter seu token de API: + 1. Faça login no aplicativo web do Fastmail + 2. Vá para Configurações > Privacidade e Segurança + 3. Clique em Integrações > Tokens de API + 4. Crie um novo token com o escopo \"Masked Email\" + 5. Copie o token e cole acima + 1. Faça login no aplicativo web do Fastmail\n2. Vá para Configurações > Privacidade e Segurança\n3. Clique em Integrações > Tokens de API\n4. Crie um novo token com o escopo \"Masked Email\"\n5. Copie o token e cole acima + + + Emails mascarados + Pesquisar emails… + Limpar pesquisa + Filtrar emails + Criar + Criar novo email mascarado + Nenhum email mascarado encontrado + Sair + Configurações + Todos + Ativados + Desativados + Excluídos + + + Criar email mascarado + Prefixo do email (opcional) + ex.: meusite_compras + Máx. 64 caracteres: letras minúsculas, números, sublinhados + Domínio associado (opcional) + ex.: exemplo.com + Descrição (opcional) + ex.: Conta de compras + URL (opcional) + ex.: https://exemplo.com + Estado inicial + Criar email mascarado + Criado: %s + Copiar + Voltar + + + Detalhes do email + Copiar endereço de email + Excluir email + Status: + Criado por: %s + Criado: %s + Última mensagem: %s + Ativar + Desativar + Editar detalhes + Descrição + Domínio associado + URL + Salvar alterações + Atualizado com sucesso + Excluído com sucesso + Copiado para a área de transferência + Excluir email mascarado + Tem certeza de que deseja excluir este email mascarado? Esta ação não pode ser desfeita. + Excluir + Cancelar + + + Ativado + Desativado + Excluído + Pendente + + + Falha ao carregar emails + Tentar novamente + Ocorreu um erro + + + Configurações + Idioma + Selecionar idioma do app + Padrão do sistema + Contato e feedback + Envie-nos seu feedback + Sair + Sair da sua conta + Versão %s + Selecionar idioma + Feedback do FastMask + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml new file mode 100644 index 0000000..e842d85 --- /dev/null +++ b/app/src/main/res/values-ru/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Менеджер масок email для Fastmail + API токен + Введите ваш API токен Fastmail + Войти + Показать токен + Скрыть токен + Как получить API токен: + 1. Войдите в веб-приложение Fastmail + 2. Перейдите в Настройки > Конфиденциальность и безопасность + 3. Нажмите Интеграции > API токены + 4. Создайте новый токен с правами \"Masked Email\" + 5. Скопируйте токен и вставьте выше + 1. Войдите в веб-приложение Fastmail\n2. Перейдите в Настройки > Конфиденциальность и безопасность\n3. Нажмите Интеграции > API токены\n4. Создайте новый токен с правами \"Masked Email\"\n5. Скопируйте токен и вставьте выше + + + Маски email + Поиск email… + Очистить поиск + Фильтровать email + Создать + Создать новую маску email + Маски email не найдены + Выйти + Настройки + Все + Активные + Отключённые + Удалённые + + + Создать маску email + Префикс email (необязательно) + напр., mysite_shopping + Макс. 64 символа: строчные буквы, цифры, подчёркивания + Связанный домен (необязательно) + напр., example.com + Описание (необязательно) + напр., Аккаунт для покупок + URL (необязательно) + напр., https://example.com + Начальное состояние + Создать маску email + Создано: %s + Копировать + Назад + + + Детали email + Копировать адрес email + Удалить email + Статус: + Создано: %s + Создано: %s + Последнее сообщение: %s + Включить + Отключить + Редактировать + Описание + Связанный домен + URL + Сохранить изменения + Успешно обновлено + Успешно удалено + Скопировано в буфер обмена + Удалить маску email + Вы уверены, что хотите удалить эту маску email? Это действие нельзя отменить. + Удалить + Отмена + + + Активна + Отключена + Удалена + Ожидание + + + Не удалось загрузить email + Повторить + Произошла ошибка + + + Настройки + Язык + Выбрать язык приложения + Системный по умолчанию + Связаться и оставить отзыв + Отправьте нам отзыв + Выйти + Выйти из аккаунта + Версия %s + Выбрать язык + Отзыв о FastMask + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml new file mode 100644 index 0000000..178148d --- /dev/null +++ b/app/src/main/res/values-th/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + ตัวจัดการอีเมลซ่อน Fastmail + โทเค็น API + ใส่โทเค็น API ของ Fastmail + เข้าสู่ระบบ + แสดงโทเค็น + ซ่อนโทเค็น + วิธีรับโทเค็น API: + 1. เข้าสู่ระบบแอป Fastmail บนเว็บ + 2. ไปที่ ตั้งค่า > ความเป็นส่วนตัวและความปลอดภัย + 3. คลิก การเชื่อมต่อ > โทเค็น API + 4. สร้างโทเค็นใหม่ที่มีขอบเขต \"Masked Email\" + 5. คัดลอกโทเค็นและวางด้านบน + 1. เข้าสู่ระบบแอป Fastmail บนเว็บ\n2. ไปที่ ตั้งค่า > ความเป็นส่วนตัวและความปลอดภัย\n3. คลิก การเชื่อมต่อ > โทเค็น API\n4. สร้างโทเค็นใหม่ที่มีขอบเขต \"Masked Email\"\n5. คัดลอกโทเค็นและวางด้านบน + + + อีเมลซ่อน + ค้นหาอีเมล… + ล้างการค้นหา + กรองอีเมล + สร้าง + สร้างอีเมลซ่อนใหม่ + ไม่พบอีเมลซ่อน + ออกจากระบบ + การตั้งค่า + ทั้งหมด + เปิดใช้งาน + ปิดใช้งาน + ลบแล้ว + + + สร้างอีเมลซ่อน + คำนำหน้าอีเมล (ไม่บังคับ) + เช่น mysite_shopping + สูงสุด 64 ตัวอักษร: ตัวพิมพ์เล็ก, ตัวเลข, ขีดล่าง + โดเมนที่เกี่ยวข้อง (ไม่บังคับ) + เช่น example.com + คำอธิบาย (ไม่บังคับ) + เช่น บัญชีช้อปปิ้ง + URL (ไม่บังคับ) + เช่น https://example.com + สถานะเริ่มต้น + สร้างอีเมลซ่อน + สร้างแล้ว: %s + คัดลอก + กลับ + + + รายละเอียดอีเมล + คัดลอกที่อยู่อีเมล + ลบอีเมล + สถานะ: + สร้างโดย: %s + สร้างเมื่อ: %s + ข้อความล่าสุด: %s + เปิดใช้งาน + ปิดใช้งาน + แก้ไขรายละเอียด + คำอธิบาย + โดเมนที่เกี่ยวข้อง + URL + บันทึกการเปลี่ยนแปลง + อัปเดตสำเร็จ + ลบสำเร็จ + คัดลอกไปยังคลิปบอร์ดแล้ว + ลบอีเมลซ่อน + คุณแน่ใจหรือไม่ว่าต้องการลบอีเมลซ่อนนี้? การกระทำนี้ไม่สามารถย้อนกลับได้ + ลบ + ยกเลิก + + + เปิดใช้งาน + ปิดใช้งาน + ลบแล้ว + รอดำเนินการ + + + ไม่สามารถโหลดอีเมลได้ + ลองอีกครั้ง + เกิดข้อผิดพลาด + + + การตั้งค่า + ภาษา + เลือกภาษาแอป + ค่าเริ่มต้นของระบบ + ติดต่อและความคิดเห็น + ส่งความคิดเห็นให้เรา + ออกจากระบบ + ออกจากบัญชีของคุณ + เวอร์ชัน %s + เลือกภาษา + ความคิดเห็น FastMask + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml new file mode 100644 index 0000000..a5dbe50 --- /dev/null +++ b/app/src/main/res/values-tr/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Fastmail Maskeli E-posta Yöneticisi + API Jetonu + Fastmail API jetonunuzu girin + Giriş Yap + Jetonu göster + Jetonu gizle + API jetonunuzu nasıl alırsınız: + 1. Fastmail web uygulamasına giriş yapın + 2. Ayarlar > Gizlilik ve Güvenlik\'e gidin + 3. Entegrasyonlar > API Jetonları\'na tıklayın + 4. \"Masked Email\" kapsamında yeni bir jeton oluşturun + 5. Jetonu kopyalayın ve yukarıya yapıştırın + 1. Fastmail web uygulamasına giriş yapın\n2. Ayarlar > Gizlilik ve Güvenlik\'e gidin\n3. Entegrasyonlar > API Jetonları\'na tıklayın\n4. \"Masked Email\" kapsamında yeni bir jeton oluşturun\n5. Jetonu kopyalayın ve yukarıya yapıştırın + + + Maskeli E-postalar + E-posta ara… + Aramayı temizle + E-postaları filtrele + Oluştur + Yeni maskeli e-posta oluştur + Maskeli e-posta bulunamadı + Çıkış Yap + Ayarlar + Tümü + Etkin + Devre Dışı + Silinmiş + + + Maskeli E-posta Oluştur + E-posta öneki (isteğe bağlı) + örn. sitem_alisveris + Maks 64 karakter: küçük harfler, rakamlar, alt çizgiler + İlişkili alan adı (isteğe bağlı) + örn. ornek.com + Açıklama (isteğe bağlı) + örn. Alışveriş hesabı + URL (isteğe bağlı) + örn. https://ornek.com + Başlangıç durumu + Maskeli E-posta Oluştur + Oluşturuldu: %s + Kopyala + Geri + + + E-posta Detayları + E-posta adresini kopyala + E-postayı sil + Durum: + Oluşturan: %s + Oluşturulma: %s + Son mesaj: %s + Etkinleştir + Devre dışı bırak + Detayları Düzenle + Açıklama + İlişkili alan adı + URL + Değişiklikleri Kaydet + Başarıyla güncellendi + Başarıyla silindi + Panoya kopyalandı + Maskeli E-postayı Sil + Bu maskeli e-postayı silmek istediğinizden emin misiniz? Bu işlem geri alınamaz. + Sil + İptal + + + Etkin + Devre Dışı + Silinmiş + Beklemede + + + E-postalar yüklenemedi + Tekrar Dene + Bir hata oluştu + + + Ayarlar + Dil + Uygulama dilini seçin + Sistem Varsayılanı + İletişim ve Geri Bildirim + Geri bildiriminizi gönderin + Çıkış Yap + Hesabınızdan çıkış yapın + Sürüm %s + Dil Seçin + FastMask Geri Bildirimi + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml new file mode 100644 index 0000000..b5c69e3 --- /dev/null +++ b/app/src/main/res/values-uk/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Менеджер масок email для Fastmail + API токен + Введіть ваш API токен Fastmail + Увійти + Показати токен + Приховати токен + Як отримати API токен: + 1. Увійдіть у веб-застосунок Fastmail + 2. Перейдіть до Налаштування > Конфіденційність і безпека + 3. Натисніть Інтеграції > API токени + 4. Створіть новий токен з правами \"Masked Email\" + 5. Скопіюйте токен і вставте вище + 1. Увійдіть у веб-застосунок Fastmail\n2. Перейдіть до Налаштування > Конфіденційність і безпека\n3. Натисніть Інтеграції > API токени\n4. Створіть новий токен з правами \"Masked Email\"\n5. Скопіюйте токен і вставте вище + + + Маски email + Пошук email… + Очистити пошук + Фільтрувати email + Створити + Створити нову маску email + Маски email не знайдено + Вийти + Налаштування + Усі + Активні + Вимкнені + Видалені + + + Створити маску email + Префікс email (необов\'язково) + напр., mysite_shopping + Макс. 64 символи: малі літери, цифри, підкреслення + Пов\'язаний домен (необов\'язково) + напр., example.com + Опис (необов\'язково) + напр., Акаунт для покупок + URL (необов\'язково) + напр., https://example.com + Початковий стан + Створити маску email + Створено: %s + Копіювати + Назад + + + Деталі email + Копіювати адресу email + Видалити email + Статус: + Створено: %s + Створено: %s + Останнє повідомлення: %s + Увімкнути + Вимкнути + Редагувати + Опис + Пов\'язаний домен + URL + Зберегти зміни + Успішно оновлено + Успішно видалено + Скопійовано до буфера обміну + Видалити маску email + Ви впевнені, що хочете видалити цю маску email? Цю дію не можна скасувати. + Видалити + Скасувати + + + Активна + Вимкнена + Видалена + Очікування + + + Не вдалося завантажити email + Повторити + Сталася помилка + + + Налаштування + Мова + Вибрати мову застосунку + Системна за замовчуванням + Зв\'язок та відгуки + Надішліть нам відгук + Вийти + Вийти з акаунту + Версія %s + Вибрати мову + Відгук про FastMask + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml new file mode 100644 index 0000000..41a4527 --- /dev/null +++ b/app/src/main/res/values-vi/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Quản lý Email ẩn danh Fastmail + Mã API + Nhập mã API Fastmail của bạn + Đăng nhập + Hiện mã + Ẩn mã + Cách lấy mã API: + 1. Đăng nhập vào ứng dụng web Fastmail + 2. Vào Cài đặt > Quyền riêng tư & Bảo mật + 3. Nhấp vào Tích hợp > Mã API + 4. Tạo mã mới với phạm vi \"Masked Email\" + 5. Sao chép mã và dán ở trên + 1. Đăng nhập vào ứng dụng web Fastmail\n2. Vào Cài đặt > Quyền riêng tư & Bảo mật\n3. Nhấp vào Tích hợp > Mã API\n4. Tạo mã mới với phạm vi \"Masked Email\"\n5. Sao chép mã và dán ở trên + + + Email ẩn danh + Tìm kiếm email… + Xóa tìm kiếm + Lọc email + Tạo mới + Tạo email ẩn danh mới + Không tìm thấy email ẩn danh + Đăng xuất + Cài đặt + Tất cả + Đang hoạt động + Đã tắt + Đã xóa + + + Tạo email ẩn danh + Tiền tố email (tùy chọn) + vd: trangweb_muasam + Tối đa 64 ký tự: chữ thường, số, gạch dưới + Tên miền liên kết (tùy chọn) + vd: example.com + Mô tả (tùy chọn) + vd: Tài khoản mua sắm + URL (tùy chọn) + vd: https://example.com + Trạng thái ban đầu + Tạo email ẩn danh + Đã tạo: %s + Sao chép + Quay lại + + + Chi tiết email + Sao chép địa chỉ email + Xóa email + Trạng thái: + Tạo bởi: %s + Ngày tạo: %s + Tin nhắn cuối: %s + Bật + Tắt + Chỉnh sửa chi tiết + Mô tả + Tên miền liên kết + URL + Lưu thay đổi + Cập nhật thành công + Xóa thành công + Đã sao chép vào clipboard + Xóa email ẩn danh + Bạn có chắc muốn xóa email ẩn danh này? Hành động này không thể hoàn tác. + Xóa + Hủy + + + Đang hoạt động + Đã tắt + Đã xóa + Đang chờ + + + Không thể tải email + Thử lại + Đã xảy ra lỗi + + + Cài đặt + Ngôn ngữ + Chọn ngôn ngữ ứng dụng + Mặc định hệ thống + Liên hệ & Phản hồi + Gửi phản hồi cho chúng tôi + Đăng xuất + Đăng xuất khỏi tài khoản + Phiên bản %s + Chọn ngôn ngữ + Phản hồi FastMask + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000..43a68df --- /dev/null +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,121 @@ + + + + FastMask + + + Fastmail 隐藏邮箱管理器 + API 令牌 + 输入您的 Fastmail API 令牌 + 登录 + 显示令牌 + 隐藏令牌 + 如何获取您的 API 令牌: + 1. 登录 Fastmail 网页版 + 2. 前往 设置 > 隐私与安全 + 3. 点击 集成 > API 令牌 + 4. 创建一个具有"隐藏邮箱"权限的新令牌 + 5. 复制令牌并粘贴到上方 + 1. 登录 Fastmail 网页版\n2. 前往 设置 > 隐私与安全\n3. 点击 集成 > API 令牌\n4. 创建一个具有"隐藏邮箱"权限的新令牌\n5. 复制令牌并粘贴到上方 + + + 隐藏邮箱 + 搜索邮箱… + 清除搜索 + 筛选邮箱 + 创建 + 创建新的隐藏邮箱 + 未找到隐藏邮箱 + 退出登录 + 设置 + 全部 + 已启用 + 已禁用 + 已删除 + + + 创建隐藏邮箱 + 邮箱前缀(可选) + 例如:mysite_shopping + 最多64个字符:小写字母、数字、下划线 + 关联域名(可选) + 例如:example.com + 描述(可选) + 例如:购物账户 + 网址(可选) + 例如:https://example.com + 初始状态 + 创建隐藏邮箱 + 已创建:%s + 复制 + 返回 + + + 邮箱详情 + 复制邮箱地址 + 删除邮箱 + 状态: + 创建者:%s + 创建时间:%s + 最后消息:%s + 启用 + 禁用 + 编辑详情 + 描述 + 关联域名 + 网址 + 保存更改 + 更新成功 + 删除成功 + 已复制到剪贴板 + 删除隐藏邮箱 + 您确定要删除此隐藏邮箱吗?此操作无法撤销。 + 删除 + 取消 + + + 已启用 + 已禁用 + 已删除 + 待处理 + + + 加载邮箱失败 + 重试 + 发生错误 + + + 设置 + 语言 + 选择应用语言 + 跟随系统 + 联系与反馈 + 向我们发送反馈 + 退出登录 + 退出您的账户 + 版本 %s + 选择语言 + FastMask 反馈 + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ca6e0be..6b1b332 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,121 @@ + FastMask + + + Fastmail Masked Email Manager + API Token + Enter your Fastmail API token + Login + Show token + Hide token + How to get your API token: + 1. Log in to Fastmail web app + 2. Go to Settings > Privacy & Security + 3. Click on Integrations > API tokens + 4. Create a new token with \"Masked Email\" scope + 5. Copy the token and paste it above + 1. Log in to Fastmail web app\n2. Go to Settings > Privacy & Security\n3. Click on Integrations > API tokens\n4. Create a new token with \"Masked Email\" scope\n5. Copy the token and paste it above + + + Masked Emails + Search emails… + Clear search + Filter emails + Create + Create new masked email + No masked emails found + Logout + Settings + All + Enabled + Disabled + Deleted + + + Create Masked Email + Email Prefix (optional) + e.g., mysite_shopping + Max 64 chars: lowercase letters, numbers, underscores + Associated Domain (optional) + e.g., example.com + Description (optional) + e.g., Shopping account + URL (optional) + e.g., https://example.com + Initial State + Create Masked Email + Created: %s + Copy + Navigate back + + + Email Details + Copy email address + Delete email + Status: + Created by: %s + Created: %s + Last message: %s + Enable + Disable + Edit Details + Description + Associated Domain + URL + Save Changes + Updated successfully + Deleted successfully + Copied to clipboard + Delete Masked Email + Are you sure you want to delete this masked email? This action cannot be undone. + Delete + Cancel + + + Enabled + Disabled + Deleted + Pending + + + Failed to load emails + Retry + An error occurred + + + Settings + Language + Select app language + System Default + Contact & Feedback + Send us your feedback + Logout + Sign out of your account + Version %s + Select Language + FastMask Feedback + + + English + 中文 + Español + हिन्दी + العربية + Português + বাংলা + Русский + 日本語 + Français + Deutsch + 한국어 + Italiano + Türkçe + Tiếng Việt + Polski + Українська + Nederlands + ไทย + Bahasa Indonesia diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml index ade0d80..4654318 100644 --- a/app/src/main/res/xml/locales_config.xml +++ b/app/src/main/res/xml/locales_config.xml @@ -1,4 +1,23 @@ + + + + + + + + + + + + + + + + + + +