Merge branch 'main' into 1404-update-datamodel-to-use-fields-based-approach

* main:
  Update changelogs for new build number
  Create release-checklist.md
  New Crowdin updates (#1443)
  Bump qs in /apps/mobile-app in the npm_and_yarn group across 1 directory
  Adjust exportable flags for passkey authentication Android activities
  Bump uri in /docs in the bundler group across 1 directory
  Update project.pbxproj objectVersion
  Create print-latest-changelogs.sh
  Update release.yml
  Create git-versioning-strategy.md
  Add 0.25.3 changelogs
  Add additional loading message feedback during passkey verification (#1440)
  Add origin verification to Android Passkey Authentication flow (#1440)
  New Crowdin updates (#1438)
This commit is contained in:
Leendert de Borst
2026-01-01 17:41:55 +01:00
37 changed files with 1999 additions and 355 deletions

View File

@@ -27,7 +27,33 @@ on:
type: boolean
jobs:
# Guard job to prevent releases from main branch
valid-release:
if: github.event_name == 'release'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check tag target
run: |
BRANCHES=$(git branch -r --contains $GITHUB_SHA)
echo "Tag is contained in:"
echo "$BRANCHES"
if ! echo "$BRANCHES" | grep -q "origin/release/"; then
echo "❌ Releases must come from a release/* branch, please recreate the release from a release branch"
exit 1
fi
echo "✅ Tag is on a release branch"
upload-install-script:
needs: [valid-release]
if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success')
runs-on: ubuntu-latest
permissions:
contents: write
@@ -43,7 +69,8 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }}
build-chrome-extension:
if: github.event_name == 'release' || inputs.build_browser_extensions
needs: [valid-release]
if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_browser_extensions)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
@@ -58,7 +85,8 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-firefox-extension:
if: github.event_name == 'release' || inputs.build_browser_extensions
needs: [valid-release]
if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_browser_extensions)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
@@ -73,7 +101,8 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-edge-extension:
if: github.event_name == 'release' || inputs.build_browser_extensions
needs: [valid-release]
if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_browser_extensions)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
@@ -88,7 +117,8 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-android-release:
if: github.event_name == 'release' || inputs.build_mobile_apps
needs: [valid-release]
if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_mobile_apps)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
@@ -107,7 +137,8 @@ jobs:
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
build-and-push-docker-multi-container:
if: github.event_name == 'release' || inputs.build_multi_container
needs: [valid-release]
if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_multi_container)
runs-on: ubuntu-latest
permissions:
contents: read
@@ -372,7 +403,8 @@ jobs:
annotations: ${{ steps.installcli-meta.outputs.annotations }}
build-and-push-docker-all-in-one:
if: github.event_name == 'release' || inputs.build_all_in_one
needs: [valid-release]
if: always() && (github.event_name != 'release' || needs.valid-release.result == 'success') && (github.event_name == 'release' || inputs.build_all_in_one)
runs-on: ubuntu-latest
permissions:
contents: read

View File

@@ -40,13 +40,13 @@
<!-- Passkey Authentication Activity -->
<activity
android:name=".credentialprovider.PasskeyAuthenticationActivity"
android:exported="true"
android:exported="false"
android:theme="@style/PasskeyRegistrationTheme" />
<!-- Passkey Registration Activity -->
<activity
android:name=".credentialprovider.PasskeyRegistrationActivity"
android:exported="true"
android:exported="false"
android:theme="@style/PasskeyRegistrationTheme"
android:windowSoftInputMode="adjustResize"
android:fitsSystemWindows="true" />

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
package net.aliasvault.app.credentialprovider
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View
@@ -8,6 +9,10 @@ import android.widget.TextView
import androidx.credentials.provider.PendingIntentHandler
import androidx.credentials.provider.ProviderGetCredentialRequest
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.aliasvault.app.R
import net.aliasvault.app.utils.Helpers
import net.aliasvault.app.vaultstore.VaultStore
@@ -109,6 +114,19 @@ class PasskeyAuthenticationActivity : FragmentActivity() {
}
}
/**
* Update the loading message displayed to the user.
*/
private fun updateLoadingMessage(messageResId: Int) {
runOnUiThread {
try {
findViewById<TextView>(R.id.loadingMessage)?.text = getString(messageResId)
} catch (e: Exception) {
Log.w(TAG, "Could not update loading message", e)
}
}
}
/**
* Process the passkey authentication request and generate assertion.
* Called after authentication (biometric or PIN) succeeds and vault is unlocked.
@@ -120,107 +138,149 @@ class PasskeyAuthenticationActivity : FragmentActivity() {
finish()
return
}
try {
// Extract passkey ID from intent
val passkeyIdString = intent.getStringExtra(
AliasVaultCredentialProviderService.EXTRA_PASSKEY_ID,
)
if (passkeyIdString == null) {
Log.e(TAG, "No passkey ID in intent")
setResult(RESULT_CANCELED)
finish()
return
}
val passkeyId = UUID.fromString(passkeyIdString.uppercase())
// Get database connection from vault (should be unlocked at this point)
val db = vaultStore.database
if (db == null) {
Log.e(TAG, "Database not available - vault may not be unlocked")
setResult(RESULT_CANCELED)
finish()
return
}
val passkey = vaultStore.getPasskeyById(passkeyId, db)
if (passkey == null) {
Log.e(TAG, "Passkey not found: $passkeyId")
setResult(RESULT_CANCELED)
finish()
return
}
val requestJson = intent.getStringExtra(
AliasVaultCredentialProviderService.EXTRA_REQUEST_JSON,
) ?: ""
val requestObj = JSONObject(requestJson)
// Extract clientDataHash from the calling app's request
// Browsers (Chrome, Firefox, Edge, etc.) provide this, native apps typically don't
val providedClientDataHash: ByteArray? = providerRequest.credentialOptions
.filterIsInstance<androidx.credentials.GetPublicKeyCredentialOption>()
.firstOrNull()?.clientDataHash
// Determine clientDataHash and clientDataJson based on what caller provided
val clientDataHash: ByteArray
val clientDataJson: String?
if (providedClientDataHash != null) {
// Browser provided clientDataHash - use it directly
// The browser has its own clientDataJSON with the web origin
clientDataHash = providedClientDataHash
clientDataJson = null
} else {
// Native app scenario - build clientDataJSON ourselves and hash it
val challenge = requestObj.optString("challenge", "")
val origin = requestObj.optString("origin", "https://${passkey.rpId}")
val json = buildClientDataJson(challenge, origin)
clientDataHash = sha256(json.toByteArray(Charsets.UTF_8))
clientDataJson = json
}
// Use PasskeyAuthenticator.getAssertion for signing
val credentialId = PasskeyHelper.guidToBytes(passkey.id.toString())
val prfInputs = extractPrfInputs(requestObj)
val assertion = PasskeyAuthenticator.getAssertion(
credentialId = credentialId,
clientDataHash = clientDataHash,
rpId = passkey.rpId,
privateKeyJWK = passkey.privateKey,
userId = passkey.userHandle,
uvPerformed = true,
prfInputs = prfInputs,
prfSecret = passkey.prfKey,
)
// Build response JSON
val response = buildPublicKeyCredentialResponse(
assertion = assertion,
clientDataJson = clientDataJson,
)
val resultIntent = Intent()
lifecycleScope.launch {
try {
PendingIntentHandler.setGetCredentialResponse(resultIntent, response)
setResult(RESULT_OK, resultIntent)
} catch (e: Exception) {
Log.e(TAG, "Error setting credential response", e)
try {
PendingIntentHandler.setGetCredentialException(
resultIntent,
androidx.credentials.exceptions.GetCredentialUnknownException("Failed to generate assertion: ${e.message}"),
)
setResult(RESULT_OK, resultIntent)
} catch (e2: Exception) {
Log.e(TAG, "Error setting exception", e2)
// Show retrieving status to user
updateLoadingMessage(R.string.passkey_retrieving)
// Extract passkey ID from intent
val passkeyIdString = intent.getStringExtra(
AliasVaultCredentialProviderService.EXTRA_PASSKEY_ID,
)
if (passkeyIdString == null) {
Log.e(TAG, "No passkey ID in intent")
setResult(RESULT_CANCELED)
finish()
return@launch
}
val passkeyId = UUID.fromString(passkeyIdString.uppercase())
// Get database connection from vault
val db = vaultStore.database
if (db == null) {
Log.e(TAG, "Database not available - vault may not be unlocked")
setResult(RESULT_CANCELED)
finish()
return@launch
}
val passkey = vaultStore.getPasskeyById(passkeyId, db)
if (passkey == null) {
Log.e(TAG, "Passkey not found: $passkeyId")
setResult(RESULT_CANCELED)
finish()
return@launch
}
val requestJson = intent.getStringExtra(
AliasVaultCredentialProviderService.EXTRA_REQUEST_JSON,
) ?: ""
val requestObj = JSONObject(requestJson)
// Extract clientDataHash from the calling app's request
// Browsers (Chrome, Firefox, Edge, etc.) provide this, native apps typically don't
val providedClientDataHash: ByteArray? = providerRequest.credentialOptions
.filterIsInstance<androidx.credentials.GetPublicKeyCredentialOption>()
.firstOrNull()?.clientDataHash
// Show verifying status to user
updateLoadingMessage(R.string.passkey_verifying)
// Verify origin of the calling app
val originVerifier = OriginVerifier()
val callingAppInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
providerRequest.callingAppInfo
} else {
null
}
// Run origin verification on IO thread
val originResult = withContext(Dispatchers.IO) {
originVerifier.verifyOrigin(
callingAppInfo = callingAppInfo,
requestedRpId = passkey.rpId,
)
}
val verifiedOrigin: String
val isPrivilegedCaller: Boolean
when (originResult) {
is OriginVerifier.OriginResult.Success -> {
verifiedOrigin = originResult.origin
isPrivilegedCaller = originResult.isPrivileged
Log.d(TAG, "Origin verified: $verifiedOrigin (privileged: $isPrivilegedCaller)")
}
is OriginVerifier.OriginResult.Failure -> {
Log.e(TAG, "Origin verification failed: ${originResult.reason}")
showError("Security error: ${originResult.reason}")
return@launch
}
}
// Show authenticating status to user
updateLoadingMessage(R.string.passkey_authenticating)
// Determine clientDataHash and clientDataJson based on what caller provided
val clientDataHash: ByteArray
val clientDataJson: String?
if (providedClientDataHash != null && isPrivilegedCaller) {
// Browser provided clientDataHash - use it directly
clientDataHash = providedClientDataHash
clientDataJson = null
} else {
// Native app scenario - build clientDataJSON ourselves
val challenge = requestObj.optString("challenge", "")
val json = buildClientDataJson(challenge, verifiedOrigin)
clientDataHash = sha256(json.toByteArray(Charsets.UTF_8))
clientDataJson = json
}
// Use PasskeyAuthenticator.getAssertion for signing
val credentialId = PasskeyHelper.guidToBytes(passkey.id.toString())
val prfInputs = extractPrfInputs(requestObj)
val assertion = PasskeyAuthenticator.getAssertion(
credentialId = credentialId,
clientDataHash = clientDataHash,
rpId = passkey.rpId,
privateKeyJWK = passkey.privateKey,
userId = passkey.userHandle,
uvPerformed = true,
prfInputs = prfInputs,
prfSecret = passkey.prfKey,
)
// Build response JSON
val response = buildPublicKeyCredentialResponse(
assertion = assertion,
clientDataJson = clientDataJson,
)
val resultIntent = Intent()
try {
PendingIntentHandler.setGetCredentialResponse(resultIntent, response)
setResult(RESULT_OK, resultIntent)
} catch (e: Exception) {
Log.e(TAG, "Error setting credential response", e)
try {
PendingIntentHandler.setGetCredentialException(
resultIntent,
androidx.credentials.exceptions.GetCredentialUnknownException("Failed to generate assertion: ${e.message}"),
)
setResult(RESULT_OK, resultIntent)
} catch (e2: Exception) {
Log.e(TAG, "Error setting exception", e2)
setResult(RESULT_CANCELED)
}
}
finish()
} catch (e: Exception) {
Log.e(TAG, "Error processing authentication request", e)
setResult(RESULT_CANCELED)
finish()
}
finish()
} catch (e: Exception) {
Log.e(TAG, "Error processing authentication request", e)
setResult(RESULT_CANCELED)
finish()
}
}

View File

@@ -246,8 +246,11 @@ class PasskeyFormFragment : Fragment() {
val requestObj = JSONObject(viewModel.requestJson)
val challenge = requestObj.optString("challenge", "")
// Use origin from the request, or fallback to RP id
val requestOrigin = viewModel.origin ?: ("https://" + viewModel.rpId)
// Use the origin set by PasskeyRegistrationActivity
val requestOrigin = viewModel.origin
?: throw net.aliasvault.app.exceptions.PasskeyOperationException(
"Origin not available",
)
// Extract PRF inputs if present
val prfInputs = extractPrfInputs(requestObj)
@@ -438,8 +441,9 @@ class PasskeyFormFragment : Fragment() {
val requestObj = JSONObject(viewModel.requestJson)
val challenge = requestObj.optString("challenge", "")
// Use origin from the request
val requestOrigin = viewModel.origin ?: throw PasskeyOperationException("Origin not available")
// Use the origin set by PasskeyRegistrationActivity
val requestOrigin = viewModel.origin
?: throw PasskeyOperationException("Origin not available")
// Extract PRF inputs if present
val prfInputs = extractPrfInputs(requestObj)

View File

@@ -1,14 +1,20 @@
package net.aliasvault.app.credentialprovider
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.TextView
import androidx.activity.viewModels
import androidx.credentials.CreatePublicKeyCredentialRequest
import androidx.credentials.provider.CallingAppInfo
import androidx.credentials.provider.PendingIntentHandler
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.aliasvault.app.R
import net.aliasvault.app.credentialprovider.models.PasskeyRegistrationViewModel
import net.aliasvault.app.utils.Helpers
@@ -65,10 +71,9 @@ class PasskeyRegistrationActivity : FragmentActivity() {
return
}
// Get requestJson, clientDataHash, and origin from the request
// Get requestJson, clientDataHash from the request
viewModel.requestJson = createRequest.requestJson
viewModel.clientDataHash = createRequest.clientDataHash
viewModel.origin = createRequest.origin
// Parse request JSON to extract RP ID and user info
val requestObj = JSONObject(viewModel.requestJson)
@@ -102,29 +107,18 @@ class PasskeyRegistrationActivity : FragmentActivity() {
null
}
// Show loading screen while unlock is in progress
// Show loading screen while verification and unlock are in progress
setContentView(R.layout.activity_loading)
// Initialize unlock coordinator
unlockCoordinator = UnlockCoordinator(
activity = this,
vaultStore = vaultStore,
onUnlocked = {
// Vault unlocked successfully - proceed with passkey registration
proceedWithPasskeyRegistration(savedInstanceState)
},
onCancelled = {
// User cancelled unlock
finish()
},
onError = { errorMessage ->
// Error during unlock
showError(errorMessage)
},
)
// Get calling app info for origin verification
val callingAppInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
providerRequest.callingAppInfo
} else {
null
}
// Start the unlock flow
unlockCoordinator.startUnlockFlow()
// Verify origin and start unlock flow
verifyOriginAndStartUnlock(callingAppInfo, savedInstanceState)
} catch (e: Exception) {
Log.e(TAG, "Error in onCreate", e)
finish()
@@ -140,6 +134,76 @@ class PasskeyRegistrationActivity : FragmentActivity() {
}
}
/**
* Update the loading message displayed to the user.
*/
private fun updateLoadingMessage(messageResId: Int) {
runOnUiThread {
try {
findViewById<TextView>(R.id.loadingMessage)?.text = getString(messageResId)
} catch (e: Exception) {
Log.w(TAG, "Could not update loading message", e)
}
}
}
/**
* Verify origin on background thread and start unlock flow if successful.
*/
private fun verifyOriginAndStartUnlock(callingAppInfo: CallingAppInfo?, savedInstanceState: Bundle?) {
lifecycleScope.launch {
try {
// Show verifying status to user (network call may happen)
updateLoadingMessage(R.string.passkey_verifying)
// Run origin verification on IO thread (asset links fetch requires network)
val originVerifier = OriginVerifier()
val originResult = withContext(Dispatchers.IO) {
originVerifier.verifyOrigin(
callingAppInfo = callingAppInfo,
requestedRpId = viewModel.rpId,
)
}
when (originResult) {
is OriginVerifier.OriginResult.Success -> {
viewModel.origin = originResult.origin
viewModel.isPrivilegedCaller = originResult.isPrivileged
Log.d(TAG, "Origin verified: ${originResult.origin} (privileged: ${originResult.isPrivileged})")
// Initialize unlock coordinator
unlockCoordinator = UnlockCoordinator(
activity = this@PasskeyRegistrationActivity,
vaultStore = vaultStore,
onUnlocked = {
// Vault unlocked successfully - proceed with passkey registration
proceedWithPasskeyRegistration(savedInstanceState)
},
onCancelled = {
// User cancelled unlock
finish()
},
onError = { errorMessage ->
// Error during unlock
showError(errorMessage)
},
)
// Start the unlock flow
unlockCoordinator.startUnlockFlow()
}
is OriginVerifier.OriginResult.Failure -> {
Log.e(TAG, "Origin verification failed: ${originResult.reason}")
showError("Security error: ${originResult.reason}")
}
}
} catch (e: Exception) {
Log.e(TAG, "Error verifying origin", e)
showError("Error verifying application: ${e.message}")
}
}
}
/**
* Proceed with passkey registration after authentication (biometric or PIN).
*/

View File

@@ -17,6 +17,9 @@ class PasskeyRegistrationViewModel : ViewModel() {
/** The origin URL of the passkey request. */
var origin: String? = null
/** Whether the caller is a privileged app (browser). */
var isPrivilegedCaller: Boolean = false
/** The relying party identifier. */
var rpId: String = ""

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
<string name="passkey_replacing">Replacing passkey…</string>
<string name="passkey_checking_connection">Checking connection…</string>
<string name="passkey_retrieving">Retrieving passkey…</string>
<string name="passkey_verifying">Verifying…</string>
<string name="passkey_authenticating">Authenticating…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">Connection Error</string>
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">Dies wird den bestehenden Passkey durch einen neuen ersetzen. Bitte beachte, dass Dein alter Passkey überschrieben wird und nicht mehr zugänglich ist. Wenn Du stattdessen einen separaten Passkey erstellen möchtest, gehe zurück zum vorherigen Schritt.</string>
<string name="passkey_replacing">Passkey ersetzen…</string>
<string name="passkey_checking_connection">Verbindung wird überprüft…</string>
<string name="passkey_retrieving">Retrieving passkey…</string>
<string name="passkey_verifying">Verifying…</string>
<string name="passkey_authenticating">Authenticating…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">Verbindungsfehler</string>
<string name="connection_error_message">Es kann keine Verbindung zum Server hergestellt werden. Bitte überprüfe Deine Internetverbindung und versuche das Erstellen des Passkeys erneut.</string>

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">Esto reemplazará la llave de acceso existente con una nueva. Tenga en cuenta que su llave de acceso antigua será sobrescrita y ya no será accesible. Si desea crear una llave de acceso separada en su lugar, vuelva a la pantalla anterior.</string>
<string name="passkey_replacing">Reemplazando llave de acceso…</string>
<string name="passkey_checking_connection">Comprobando la conexión…</string>
<string name="passkey_retrieving">Recuperando llave…</string>
<string name="passkey_verifying">Verificando…</string>
<string name="passkey_authenticating">Autenticando…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">Error de conexión</string>
<string name="connection_error_message">No se puede establecer conexión con el servidor. Por favor, compruebe su conexión a Internet e intente crear la llave de acceso de nuevo.</string>

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">Tämä korvaa olemassa olevan todennusavaimen uudella todennusavaimella. Ole hyvä ja ota huomioon, että vanha todennusavaimesi on korvattu eikä enää käytettävissä. Jos haluat luoda erillisen todennusavaimen sen sijaan, mene takaisin edelliseen ruutuun.</string>
<string name="passkey_replacing">Korvataan todennusavainta...</string>
<string name="passkey_checking_connection">Tarkistetaan yhteyttä</string>
<string name="passkey_retrieving">Noudetaan todennusavainta...</string>
<string name="passkey_verifying">Tarkistetaan…</string>
<string name="passkey_authenticating">Todennetaan…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">Yhteysvirhe</string>
<string name="connection_error_message">Yhteyttä palvelimeen ei voida luoda. Tarkista internet-yhteytesi ja yritä luoda todennusavain uudelleen.</string>

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
<string name="passkey_replacing">Replacing passkey…</string>
<string name="passkey_checking_connection">Checking connection…</string>
<string name="passkey_retrieving">Retrieving passkey…</string>
<string name="passkey_verifying">Verifying…</string>
<string name="passkey_authenticating">Authenticating…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">Connection Error</string>
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
<string name="passkey_replacing">Replacing passkey…</string>
<string name="passkey_checking_connection">החיבור נבדק…</string>
<string name="passkey_retrieving">Retrieving passkey…</string>
<string name="passkey_verifying">Verifying…</string>
<string name="passkey_authenticating">Authenticating…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">שגיאת חיבור</string>
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">Questo sostituirà la passkey esistente con una nuova. Si prega di notare che la vecchia passkey sarà sovrascritta e non sarà più accessibile. Se si desidera invece creare una passkey separata, tornare alla schermata precedente.</string>
<string name="passkey_replacing">Sostituzione passkey…</string>
<string name="passkey_checking_connection">Controllo connessione…</string>
<string name="passkey_retrieving">Recupero passkey…</string>
<string name="passkey_verifying">Verifica…</string>
<string name="passkey_authenticating">Autenticazione…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">Errore Di Connessione</string>
<string name="connection_error_message">Non è possibile effettuare alcuna connessione al server. Controlla la tua connessione internet e prova a creare nuovamente la passkey.</string>

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">Dit zal de bestaande passkey vervangen door een nieuwe. Houd er rekening mee dat je oude passkey wordt overschreven en niet langer toegankelijk is. Als je in plaats hiervan een aparte passkey wilt maken, ga dan terug naar het vorige scherm.</string>
<string name="passkey_replacing">Passkey vervangen…</string>
<string name="passkey_checking_connection">Verbinding controleren…</string>
<string name="passkey_retrieving">Passkey ophalen…</string>
<string name="passkey_verifying">Verifiëren…</string>
<string name="passkey_authenticating">Authenticeren…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">Verbindingsfout</string>
<string name="connection_error_message">Er kan geen verbinding met de server worden gemaakt. Controleer je internetverbinding en probeer het opnieuw.</string>

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">Spowoduje to zastąpienie dotychczasowego klucza dostępu nowym. Należy pamiętać, że stare klucz zostanie nadpisany i nie będzie już dostępny. Jeśli chcesz utworzyć nowy klucz dostępu, wróć do poprzedniego ekranu.</string>
<string name="passkey_replacing">Zastępowanie klucza dostępu…</string>
<string name="passkey_checking_connection">Sprawdzanie połączenia…</string>
<string name="passkey_retrieving">Retrieving passkey…</string>
<string name="passkey_verifying">Verifying…</string>
<string name="passkey_authenticating">Authenticating…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">Błąd połączenia</string>
<string name="connection_error_message">Nie można nawiązać połączenia z serwerem. Sprawdź połączenie internetowe i spróbuj ponownie utworzyć klucz dostępu.</string>

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">Isto irá substituir a passkey existente com uma nova. Por favor, saiba que sua passkey anterior será sobrescrita e não será mais acessível. Se você deseja criar uma passkey separadamente, volte à tela anterior.</string>
<string name="passkey_replacing">Substituindo passkey…</string>
<string name="passkey_checking_connection">Verificando conexão…</string>
<string name="passkey_retrieving">Retrieving passkey…</string>
<string name="passkey_verifying">Verifying…</string>
<string name="passkey_authenticating">Authenticating…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">Erro de Conexão</string>
<string name="connection_error_message">A conexão com o servidor não foi feita. Por favor, confira sua conexão com a internet e tente criar a passkey novamente.</string>

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">Существующий ключ доступа будет заменен на новый. Обратите внимание, что старый ключ будет перезаписан и станет недоступен. Если вы хотите создать отдельный ключ доступа, вернитесь на предыдущий экран.</string>
<string name="passkey_replacing">Замена ключа доступа…</string>
<string name="passkey_checking_connection">Проверка соединения…</string>
<string name="passkey_retrieving">Retrieving passkey…</string>
<string name="passkey_verifying">Verifying…</string>
<string name="passkey_authenticating">Authenticating…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">Ошибка подключения</string>
<string name="connection_error_message">Не удалось подключиться к серверу. Проверьте интернет-соединение и попробуйте создать ключ доступа снова.</string>

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
<string name="passkey_replacing">Replacing passkey…</string>
<string name="passkey_checking_connection">Checking connection…</string>
<string name="passkey_retrieving">Retrieving passkey…</string>
<string name="passkey_verifying">Verifying…</string>
<string name="passkey_authenticating">Authenticating…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">Connection Error</string>
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
<string name="passkey_replacing">Replacing passkey…</string>
<string name="passkey_checking_connection">Checking connection…</string>
<string name="passkey_retrieving">Retrieving passkey…</string>
<string name="passkey_verifying">Verifying…</string>
<string name="passkey_authenticating">Authenticating…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">Connection Error</string>
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
<string name="passkey_replacing">Replacing passkey…</string>
<string name="passkey_checking_connection">Checking connection…</string>
<string name="passkey_retrieving">Retrieving passkey…</string>
<string name="passkey_verifying">Verifying…</string>
<string name="passkey_authenticating">Authenticating…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">Connection Error</string>
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>

View File

@@ -43,6 +43,9 @@
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
<string name="passkey_replacing">正在替换通行密钥…</string>
<string name="passkey_checking_connection">检查连接中…</string>
<string name="passkey_retrieving">Retrieving passkey…</string>
<string name="passkey_verifying">Verifying…</string>
<string name="passkey_authenticating">Authenticating…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">连接错误</string>
<string name="connection_error_message">No connection to the server can be made. Please check your internet connection and try creating the passkey again.</string>

View File

@@ -49,6 +49,9 @@
<string name="passkey_replace_explanation">This will replace the existing passkey with a new one. Please be aware that your old passkey will be overwritten and no longer accessible. If you wish to create a separate passkey instead, go back to the previous screen.</string>
<string name="passkey_replacing">Replacing passkey…</string>
<string name="passkey_checking_connection">Checking connection…</string>
<string name="passkey_retrieving">Retrieving passkey…</string>
<string name="passkey_verifying">Verifying…</string>
<string name="passkey_authenticating">Authenticating…</string>
<!-- Vault sync error messages -->
<string name="connection_error_title">Connection Error</string>

View File

@@ -1,7 +1,7 @@
{
"common": {
"cancel": "Annuler",
"close": "Close",
"close": "Fermer",
"delete": "Supprimer",
"save": "Sauvegarder",
"yes": "Oui",
@@ -24,78 +24,78 @@
"deleteItemConfirmTitle": "Supprimer l'élement",
"deleteItemConfirmDescription": "Êtes-vous certain de vouloir supprimer cet élément?",
"errors": {
"unknownError": "An unknown error occurred. Please try again.",
"unknownErrorTryAgain": "An unknown error occurred. Please try again.",
"serverVersionTooOld": "The AliasVault server needs to be updated to a newer version in order to use this feature. Please contact the server admin if you need help."
"unknownError": "Une erreur inconnue s'est produite. Merci de réessayer.",
"unknownErrorTryAgain": "Une erreur inconnue s'est produite. Merci de réessayer.",
"serverVersionTooOld": "Le serveur AliasVault doit être mis à jour vers une version plus récente pour pouvoir utiliser cette fonctionnalité. Veuillez contacter l'administrateur du serveur si vous avez besoin d'aide."
}
},
"auth": {
"login": "Log in",
"logout": "Logout",
"username": "Username or email",
"password": "Password",
"authCode": "Authentication Code",
"unlock": "Unlock",
"unlocking": "Unlocking...",
"loggingIn": "Logging in",
"validatingCredentials": "Validating credentials",
"syncingVault": "Syncing vault",
"verifyingAuthCode": "Verifying authentication code",
"verify": "Verify",
"unlockVault": "Unlock Vault",
"unlockWithPin": "Unlock with PIN",
"enterPassword": "Enter your password to unlock your vault",
"enterPasswordPlaceholder": "Password",
"enterAuthCode": "Enter 6-digit code",
"login": "Se connecter",
"logout": "Se déconnecter",
"username": "Nom d'utilisateur ou email",
"password": "Mot de passe",
"authCode": "Code d'authentification",
"unlock": "Déverrouiller",
"unlocking": "Déverrouillage...",
"loggingIn": "Connexion en cours",
"validatingCredentials": "Validation des identifiants",
"syncingVault": "Synchronisation du coffre",
"verifyingAuthCode": "Vérification du code d'authentification",
"verify": "Vérifier",
"unlockVault": "Déverrouiller le coffre",
"unlockWithPin": "Déverrouiller avec un code PIN",
"enterPassword": "Entrez votre mot de passe principal pour déverrouiller votre coffre-fort",
"enterPasswordPlaceholder": "Mot de passe",
"enterAuthCode": "Saisissez le code à 6 chiffres",
"usernamePlaceholder": "nom / nom@entreprise.com",
"passwordPlaceholder": "Enter your password",
"enableBiometric": "Enable {{biometric}}?",
"biometricPrompt": "Would you like to use {{biometric}} to unlock your vault?",
"tryBiometricAgain": "Try {{biometric}} Again",
"tryPinAgain": "Try PIN Again",
"authCodeNote": "Note: if you don't have access to your authenticator device, you can reset your 2FA with a recovery code by logging in via the website.",
"passwordPlaceholder": "Saisissez votre mot de passe",
"enableBiometric": "Activer {{biometric}}?",
"biometricPrompt": "Voulez-vous utiliser {{biometric}} pour déverrouiller votre coffre ?",
"tryBiometricAgain": "Réessayez {{biometric}}",
"tryPinAgain": "Réessayez le PIN",
"authCodeNote": "Remarque : si vous n'avez pas accès à votre appareil d'authentification, vous pouvez réinitialiser votre authentification 2FA avec un code de récupération en vous connectant via le site web.",
"errors": {
"credentialsRequired": "Username and password are required",
"invalidAuthCode": "Please enter a valid 6-digit authentication code",
"credentialsRequired": "Le nom d'utilisateur et le mot de passe sont requis",
"invalidAuthCode": "Veuillez entrer un code d'authentification à 6 chiffres valide",
"incorrectPassword": "Mot de passe incorrect. Veuillez réessayer.",
"enterPassword": "Please enter your password",
"serverError": "Could not reach AliasVault server. Please try again later or contact support if the problem persists.",
"serverErrorSelfHosted": "Could not reach the API. For self-hosted instances, please verify the API endpoint is reachable by navigating to it in a browser: it should display 'OK'.",
"networkError": "Network request failed. Please check your internet connection and try again.",
"networkErrorSelfHosted": "Network request failed. Check your network connection and server availability. For self-hosted instances, please ensure you have a valid SSL certificate installed. Self-signed certificates are not supported on mobile devices for security reasons.",
"sessionExpired": "Your session has expired. Please login again.",
"httpError": "HTTP error: {{status}}"
"enterPassword": "Veuillez saisir votre mot de passe",
"serverError": "Impossible d'accéder au serveur AliasVault. Veuillez réessayer plus tard ou contacter le support si le problème persiste.",
"serverErrorSelfHosted": "Impossible d'atteindre l'API. Pour les instances auto-hébergées, veuillez vérifier que le point de terminaison de l'API est accessible en naviguant vers celui-ci dans un navigateur : il devrait afficher 'OK'.",
"networkError": "Erreur réseau. Vérifiez votre connexion et réessayez.",
"networkErrorSelfHosted": "La requête réseau a échoué. Vérifiez votre connexion réseau et la disponibilité du serveur. Pour les instances auto-hébergées, veuillez vous assurer que vous avez un certificat SSL valide. Les certificats auto-signés ne sont pas pris en charge pour des raisons de sécurité.",
"sessionExpired": "Votre session a expiré. Veuillez vous reconnecter.",
"httpError": "Erreur HTTP : {{status}}"
},
"confirmLogout": "Are you sure you want to logout? You need to login again with your master password to access your vault.",
"confirmLogout": "Êtes-vous sûr de vouloir vous déconnecter ? Vous devez vous reconnecter avec votre mot de passe maître pour accéder à votre coffre.",
"noAccountYet": "Pas encore de compte ?",
"createNewVault": "Créer un nouveau coffre-fort",
"connectingTo": "Connexion à",
"loggedInAs": "Connecté en tant que"
},
"vault": {
"syncingVault": "Syncing vault",
"uploadingVaultToServer": "Uploading vault to server",
"savingChangesToVault": "Saving changes to vault",
"checkingForVaultUpdates": "Checking for vault updates",
"executingOperation": "Executing operation...",
"checkingVaultUpdates": "Checking vault updates",
"syncingUpdatedVault": "Syncing updated vault",
"syncingVault": "Synchronisation du coffre",
"uploadingVaultToServer": "Envoi du coffre sur le serveur",
"savingChangesToVault": "Enregistrement des modifications dans le coffre",
"checkingForVaultUpdates": "Vérification des mises à jour du coffre",
"executingOperation": "Exécution de l'opération...",
"checkingVaultUpdates": "Vérification des mises à jour du coffre",
"syncingUpdatedVault": "Synchronisation du coffre mis à jour",
"errors": {
"failedToGetEncryptedDatabase": "Failed to get encrypted database",
"usernameNotFound": "Username not found",
"vaultOutdated": "Your vault is outdated. Please login on the AliasVault website and follow the steps.",
"failedToSyncVault": "Failed to sync vault",
"versionNotSupported": "This version of the AliasVault mobile app is not supported by the server anymore. Please update your app to the latest version.",
"serverVersionNotSupported": "The AliasVault server needs to be updated to a newer version in order to use this mobile app. Please contact support if you need help.",
"failedToGetEncryptedDatabase": "Impossible d'obtenir la base de données chiffrée",
"usernameNotFound": "Identifiant non trouvé",
"vaultOutdated": "Votre coffre est obsolète. Veuillez vous connecter sur le site AliasVault et suivre les étapes.",
"failedToSyncVault": "Échec de la synchronisation du coffre",
"versionNotSupported": "Cette version de l'application mobile AliasVault n'est plus prise en charge par le serveur. Veuillez mettre à jour votre application vers la dernière version.",
"serverVersionNotSupported": "Le serveur AliasVault doit être mis à jour vers une version plus récente afin d'utiliser cette application mobile. Veuillez contacter le support si vous avez besoin d'aide.",
"appOutdated": "Cette application est obsolète et ne peut pas être utilisée pour accéder à cette (nouvelle) version du coffre. Veuillez mettre à jour l'application AliasVault pour continuer.",
"passwordChanged": "Your password has changed since the last time you logged in. Please login again for security reasons."
"passwordChanged": "Votre mot de passe a changé depuis la dernière fois que vous vous êtes connecté. Veuillez vous reconnecter pour des raisons de sécurité."
}
},
"credentials": {
"title": "Credentials",
"addCredential": "Add Credential",
"editCredential": "Edit Credential",
"deleteCredential": "Delete Credential",
"title": "Identifiants",
"addCredential": "Ajouter un identifiant",
"editCredential": "Modifier l'identifiant",
"deleteCredential": "Supprimer l'identifiant",
"deleteConfirm": "Êtes-vous sûr de vouloir supprimer ces identifiants ? Cette action est irréversible.",
"service": "Service",
"serviceName": "Nom du service",
@@ -113,116 +113,116 @@
"birthDate": "Date de naissance",
"birthDatePlaceholder": "AAAA-MM-JJ",
"notes": "Notes",
"randomAlias": "Random Alias",
"manual": "Manual",
"generateRandomAlias": "Generate Random Alias",
"clearAliasFields": "Clear Alias Fields",
"enterFullEmail": "Enter full email address",
"enterEmailPrefix": "Enter email prefix",
"useDomainChooser": "Use domain chooser",
"enterCustomDomain": "Enter custom domain",
"selectEmailDomain": "Select Email Domain",
"privateEmailTitle": "Private Email",
"privateEmailAliasVaultServer": "AliasVault server",
"privateEmailDescription": "E2E encrypted, fully private.",
"publicEmailTitle": "Public Temp Email Providers",
"publicEmailDescription": "Anonymous but limited privacy. Email content is readable by anyone that knows the address.",
"searchPlaceholder": "Search vault...",
"noMatchingCredentials": "No matching credentials found",
"noCredentialsFound": "No credentials found. Create one to get started. Tip: you can also login to the AliasVault web app to import credentials from other password managers.",
"noPasskeysFound": "No passkeys have been created yet. Passkeys are created by visiting a website that offers passkeys as an authentication method.",
"noAttachmentsFound": "No credentials with attachments found",
"recentEmails": "Recent emails",
"loadingEmails": "Loading emails...",
"noEmailsYet": "No emails received yet.",
"offlineEmailsMessage": "You are offline. Please connect to the internet to load your emails.",
"emailLoadError": "An error occurred while loading emails. Please try again later.",
"emailUnexpectedError": "An unexpected error occurred while loading emails. Please try again later.",
"password": "Password",
"passwordLength": "Password Length",
"changePasswordComplexity": "Password Settings",
"includeLowercase": "Lowercase (a-z)",
"includeUppercase": "Uppercase (A-Z)",
"includeNumbers": "Numbers (0-9)",
"includeSpecialChars": "Special Characters (!@#)",
"avoidAmbiguousChars": "Avoid Ambiguous Characters",
"deletingCredential": "Deleting credential...",
"errorLoadingCredentials": "Error loading credentials",
"vaultSyncFailed": "Vault sync failed",
"vaultSyncedSuccessfully": "Vault synced successfully",
"vaultUpToDate": "Vault is up-to-date",
"offlineMessage": "You are offline. Please connect to the internet to sync your vault.",
"credentialCreated": "Credential Created!",
"credentialCreatedMessage": "Your new credential has been added to your vault and is ready to use.",
"credentialDetails": "Credential Details",
"emailPreview": "Email Preview",
"switchBackToBrowser": "Switch back to your browser to continue.",
"randomAlias": "Alias aléatoire",
"manual": "Manuel",
"generateRandomAlias": "Générer un alias aléatoire",
"clearAliasFields": "Effacer les champs d'alias",
"enterFullEmail": "Entrez l'adresse email complète",
"enterEmailPrefix": "Entrez le préfixe de l'email",
"useDomainChooser": "Utiliser le sélecteur de domaine",
"enterCustomDomain": "Entrez le domaine personnalisé",
"selectEmailDomain": "Sélectionner un domaine de messagerie",
"privateEmailTitle": "E-mail privé",
"privateEmailAliasVaultServer": "Serveur AliasVault",
"privateEmailDescription": "E2E chiffré, entièrement privé.",
"publicEmailTitle": "Fournisseurs d'email public temporaires",
"publicEmailDescription": "Anonyme mais confidentiel limitée. Le contenu des e-mails est lisible par toute personne qui connaît l'adresse.",
"searchPlaceholder": "Rechercher dans le coffre...",
"noMatchingCredentials": "Aucun identifiant correspondant trouvé",
"noCredentialsFound": "Aucun identifiant trouvé. Créez en un pour commencer. Astuce : vous pouvez également vous connecter à l'application web AliasVault pour importer les identifiants depuis d'autres gestionnaires de mots de passe.",
"noPasskeysFound": "Aucune clé d'accès n'a encore été créée. Les clés d'accès sont créés en visitant un site Web qui propose des clés d'accès comme méthode d'authentification.",
"noAttachmentsFound": "Aucun identifiant avec des pièces jointes trouvé",
"recentEmails": "E-mails récents",
"loadingEmails": "Chargement des e-mails...",
"noEmailsYet": "Pas encore d'e-mails reçus.",
"offlineEmailsMessage": "Vous êtes déconnecté. Veuillez vous connecter à internet pour charger vos e-mails.",
"emailLoadError": "Une erreur s'est produite lors du chargement des e-mails. Veuillez réessayer plus tard.",
"emailUnexpectedError": "Une erreur inattendue s'est produite lors du chargement des e-mails. Veuillez réessayer plus tard.",
"password": "Mot de passe",
"passwordLength": "Longueur du mot de passe",
"changePasswordComplexity": "Paramètres du mot de passe",
"includeLowercase": "Minuscules (a-z)",
"includeUppercase": "Majuscules (A-Z)",
"includeNumbers": "Nombres (0-9)",
"includeSpecialChars": "Caractères spéciaux (!@#)",
"avoidAmbiguousChars": "Éviter les caractères ambigus",
"deletingCredential": "Suppression de l'identifiant...",
"errorLoadingCredentials": "Erreur lors du chargement des identifiants",
"vaultSyncFailed": "Échec de la synchronisation du coffre",
"vaultSyncedSuccessfully": "Le coffre a été synchronisé avec succès",
"vaultUpToDate": "Le coffre est à jour",
"offlineMessage": "Vous êtes déconnecté. Veuillez vous connecter à internet pour synchroniser votre coffre.",
"credentialCreated": "Identifiant créé!",
"credentialCreatedMessage": "Votre nouvel identifiant a été ajouté à votre coffre et est prêt à être utilisé.",
"credentialDetails": "Détails de l'identifiant",
"emailPreview": "Aperçu de l'e-mail",
"switchBackToBrowser": "Passez à votre navigateur pour continuer.",
"filters": {
"all": "(All) Credentials",
"passkeys": "Passkeys",
"aliases": "Aliases",
"userpass": "Passwords",
"attachments": "Attachments"
"all": "(Tous les) Identifiants de connexion",
"passkeys": "Clés d'accès",
"aliases": "Alias",
"userpass": "Mots de passe",
"attachments": "Pièces jointes"
},
"twoFactorAuth": "Two-factor authentication",
"totpCode": "TOTP Code",
"attachments": "Attachments",
"deleteAttachment": "Delete",
"fileSavedTo": "File saved to",
"previewNotSupported": "Preview not supported",
"downloadToView": "Download the file to view it",
"twoFactorAuth": "Authentification à deux facteurs",
"totpCode": "Code à usage unique",
"attachments": "Pièces jointes",
"deleteAttachment": "Supprimer",
"fileSavedTo": "Fichier enregistré sous",
"previewNotSupported": "Aperçu non supporté",
"downloadToView": "Télécharger le fichier pour le voir",
"unsavedChanges": {
"title": "Discard Changes?",
"message": "You have unsaved changes. Are you sure you want to discard them?",
"discard": "Discard"
"title": "Annuler les modifications?",
"message": "Vos modifications n'ont pas été enregistrées. Voulez-vous vraiment les ignorer ?",
"discard": "Ignorer"
},
"toasts": {
"credentialUpdated": "Credential updated successfully",
"credentialCreated": "Credential created successfully",
"credentialDeleted": "Credential deleted successfully",
"usernameCopied": "Username copied to clipboard",
"emailCopied": "Email copied to clipboard",
"passwordCopied": "Password copied to clipboard"
"credentialUpdated": "Identifiant mis à jour avec succès",
"credentialCreated": "Identifiant créé avec succès",
"credentialDeleted": "Identifiant supprimé avec succès",
"usernameCopied": "Nom d'utilisateur copié dans le presse-papiers",
"emailCopied": "E-mail copié dans le presse-papiers",
"passwordCopied": "Mot de passe copié dans le presse-papiers"
},
"createNewAliasFor": "Create new alias for",
"createNewAliasFor": "Créer un nouvel alias pour",
"errors": {
"loadFailed": "Failed to load credential",
"saveFailed": "Failed to save credential"
"loadFailed": "Échec du chargement de l'identifiant",
"saveFailed": "Échec de l'enregistrement de l'identifiant"
},
"contextMenu": {
"title": "Credential Options",
"edit": "Edit",
"delete": "Delete",
"copyUsername": "Copy Username",
"copyEmail": "Copy Email",
"copyPassword": "Copy Password"
"title": "Options de l'identifiant",
"edit": "Modifier",
"delete": "Supprimer",
"copyUsername": "Copier le nom d'utilisateur",
"copyEmail": "Copier l'e-mail",
"copyPassword": "Copier le mot de passe"
}
},
"passkeys": {
"passkey": "Passkey",
"passkey": "Clé d'identification",
"site": "Site",
"displayName": "Display Name",
"helpText": "Passkeys are created on the website when prompted. They cannot be manually edited. To remove this passkey, you can delete it from this credential.",
"passkeyMarkedForDeletion": "Passkey marked for deletion",
"passkeyWillBeDeleted": "This passkey will be deleted when you save this credential."
"displayName": "Nom affiché",
"helpText": "Les clés d'accès sont créées sur le site Web lorsque vous y êtes invité. Elles ne peuvent pas être modifiées manuellement. Pour supprimer cette clé, vous pouvez la supprimer de cet identifiant.",
"passkeyMarkedForDeletion": "Clé d'accès marquée pour suppression",
"passkeyWillBeDeleted": "Cette clé d'accès sera supprimée lorsque vous enregistrerez cet identifiant."
},
"totp": {
"addCode": "Add 2FA Code",
"nameOptional": "Name (optional)",
"secretKey": "Secret Key",
"instructions": "Enter the secret key shown by the website where you want to add two-factor authentication.",
"saveToViewCode": "Save to view code",
"addCode": "Ajouter un code 2FA",
"nameOptional": "Nom (facultatif)",
"secretKey": "Clé secrète",
"instructions": "Entrez la clé secrète affichée par le site Web où vous souhaitez ajouter l'authentification à deux facteurs.",
"saveToViewCode": "Enregistrer pour afficher le code",
"errors": {
"invalidSecretKey": "Invalid secret key format."
"invalidSecretKey": "Format de clé secrète invalide."
}
},
"settings": {
"title": "Settings",
"autofill": "Autofill & Passkeys",
"title": "Réglages",
"autofill": "Remplissage automatique et clés d'accès",
"iosAutofillSettings": {
"headerText": "You can configure AliasVault to provide native password and passkey autofill functionality in iOS. Follow the instructions below to enable it.",
"passkeyNotice": "Passkeys are created through iOS. To store them in AliasVault, ensure Autofill below is enabled.",
"howToEnable": "How to enable Autofill & Passkeys:",
"headerText": "Vous pouvez configurer AliasVault pour fournir des fonctionnalités natives de saisie automatique du mot de passe et de clés d'accès dans iOS. Suivez les instructions ci-dessous pour l'activer.",
"passkeyNotice": "Les clés d'accès sont créées via iOS. Pour les stocker dans AliasVault, assurez-vous que le remplissage automatique ci-dessous est activé.",
"howToEnable": "Comment activer le remplissage automatique et les clés d'accès :",
"step1": "1. Ouvrir les paramètres d'iOS via le bouton ci-dessous",
"step2": "2. Aller dans « Général »",
"step3": "3. Appuyer sur « Remplissage automatique et mots de passe »",
@@ -233,45 +233,45 @@
"warningText": "Note : Vous devrez vous authentifier avec Face ID/Touch ID ou votre code d'accès lorsque vous utilisez le remplissage automatique."
},
"androidAutofillSettings": {
"warningTitle": "⚠️ Experimental Feature",
"warningDescription": "Autofill and passkey support for Android is currently in an experimental state.",
"warningLink": "Read more about it here",
"headerText": "You can configure AliasVault to provide native password and passkey autofill functionality in Android. Follow the instructions below to enable it.",
"passkeyNotice": "Passkeys are created through Android Credential Manager (Android 14+). To store them in AliasVault, ensure Autofill below is enabled.",
"howToEnable": "How to enable Autofill & Passkeys:",
"step1": "1. Open Android Settings via the button below, and change the \"autofill preferred service\" to \"AliasVault\"",
"openAutofillSettings": "Open Autofill Settings",
"buttonTip": "If the button above doesn't work it might be blocked because of security settings. You can manually go to Android Settings → General Management → Passwords and autofill.",
"step2": "2. Some apps, e.g. Google Chrome, may require manual configuration in their settings to allow third-party autofill apps. However, most apps should work with autofill by default.",
"alreadyConfigured": "I already configured it",
"advancedOptions": "Advanced Options",
"showSearchText": "Show search text",
"showSearchTextDescription": "Include the text AliasVault receives from Android that it uses to search for a matching credential"
"warningTitle": "⚠️ Fonctionnalité expérimentale",
"warningDescription": "Le remplissage automatique et la prise en charge de la clé d'accès pour Android sont actuellement dans un état expérimental.",
"warningLink": "En savoir plus à ce sujet ici",
"headerText": "Vous pouvez configurer AliasVault pour fournir le mot de passe natif et la fonctionnalité de saisie automatique du mot de passe dans Android. Suivez les instructions ci-dessous pour l'activer.",
"passkeyNotice": "Les mots de passe sont créés via le gestionnaire d'identifiants Android (Android 14+). Pour les stocker dans AliasVault, assurez-vous que le remplissage automatique ci-dessous est activé.",
"howToEnable": "Comment activer le remplissage automatique et les clés d'accès:",
"step1": "1. Ouvrez les paramètres Android via le bouton ci-dessous, et changez le \"service préféré de saisie automatique\" par \"AliasVault\"",
"openAutofillSettings": "Ouvrir les paramètres de remplissage automatique",
"buttonTip": "Si le bouton ci-dessus ne fonctionne pas, il peut être bloqué en raison des paramètres de sécurité. Vous pouvez manuellement aller dans Réglages Android → Gestion Générale → Mots de passe et saisie automatique.",
"step2": "2. Certaines applications, par exemple Google Chrome, peuvent nécessiter une configuration manuelle dans leurs paramètres pour permettre le remplissage automatique des applications tierces. Cependant, la plupart des applications devraient fonctionner avec le remplissage automatique par défaut.",
"alreadyConfigured": "Je l'ai déjà configuré",
"advancedOptions": "Options avancées",
"showSearchText": "Afficher le texte de recherche",
"showSearchTextDescription": "Inclure le texte que AliasVault reçoit d'Android qu'il utilise pour rechercher un identifiant correspondant"
},
"vaultUnlock": "Méthode de déverrouillage du coffre-fort",
"autoLock": "Délai de verrouillage automatique",
"clipboardClear": "Clear Clipboard",
"clipboardClearDescription": "Automatically clear copied passwords and sensitive information from your clipboard after a specified time period.",
"clipboardClearAndroidWarning": "Note: some Android devices have clipboard history enabled, which may keep track of previously copied items, even after AliasVault clears the clipboard. AliasVault can only overwrite the most recent item, but older entries may remain visible in history. For security reasons, we recommend disabling any clipboard history features in your device settings.",
"clipboardClear": "Effacer le presse-papiers",
"clipboardClearDescription": "Effacer automatiquement les mots de passe copiés et les informations sensibles du presse-papiers après une période donnée.",
"clipboardClearAndroidWarning": "Remarque : certains appareils Android ont l'historique du presse-papiers activé, qui peut garder une trace des éléments précédemment copiés, même après que AliasVault a effacé le presse-papiers. AliasVault ne peut que remplacer l'élément le plus récent, mais les entrées plus anciennes peuvent rester visibles dans l'historique. Pour des raisons de sécurité, nous vous recommandons de désactiver toutes les fonctionnalités d'historique du presse-papiers dans les paramètres de votre appareil.",
"clipboardClearOptions": {
"never": "Never",
"5seconds": "5 seconds",
"10seconds": "10 seconds",
"15seconds": "15 seconds",
"30seconds": "30 seconds"
"never": "Jamais",
"5seconds": "5 secondes",
"10seconds": "10 secondes",
"15seconds": "15 secondes",
"30seconds": "30 secondes"
},
"batteryOptimizationHelpTitle": "Enable Background Clipboard Clearing",
"batteryOptimizationActive": "Battery optimization is blocking background tasks",
"batteryOptimizationDisabled": "Background clipboard clearing enabled",
"batteryOptimizationHelpDescription": "Android's battery optimization prevents reliable clipboard clearing when the app is in the background. Disabling battery optimization for AliasVault allows precise background clipboard clearing and automatically grants necessary alarm permissions.",
"disableBatteryOptimization": "Disable battery optimization",
"batteryOptimizationHelpTitle": "Activer le nettoyage du presse-papier en arrière-plan",
"batteryOptimizationActive": "L'optimisation de la batterie bloque les tâches en arrière-plan",
"batteryOptimizationDisabled": "Effacement du presse-papiers en arrière-plan activé",
"batteryOptimizationHelpDescription": "L'optimisation de la batterie d'Android empêche le nettoyage fiable du presse-papiers lorsque l'application est en arrière-plan. La désactivation de l'optimisation de la batterie pour AliasVault permet un nettoyage précis du presse-papiers et accorde automatiquement les autorisations d'alarme nécessaires.",
"disableBatteryOptimization": "Désactiver l'optimisation de la batterie",
"identityGenerator": "Générateur d'identité",
"passwordGenerator": "Password Generator",
"importExport": "Import / Export",
"importSectionTitle": "Import",
"importSectionDescription": "Import your passwords from other password managers or from a previous AliasVault export.",
"importWebNote": "To import credentials from existing password managers, please login to the web app. The import feature is currently only available on the web version.",
"exportSectionTitle": "Export",
"passwordGenerator": "Générateur de mot de passe",
"importExport": "Importer / Exporter",
"importSectionTitle": "Importer",
"importSectionDescription": "Importez vos mots de passe depuis d'autres gestionnaires de mots de passe ou depuis un précédent export AliasVault.",
"importWebNote": "Pour importer des informations didentification à partir des gestionnaires de mots de passe existants, veuillez vous connecter à lapplication Web. La fonction dimportation nest actuellement disponible que sur la version web.",
"exportSectionTitle": "Exporter",
"exportSectionDescription": "Export your vault data to a CSV file. This file can be used as a back-up and can also be imported into other password managers.",
"exportCsvButton": "Export vault to CSV file",
"exporting": "Exporting...",
@@ -415,7 +415,7 @@
"invalidQrCode": "Invalid QR Code",
"notAliasVaultQr": "This is not a valid AliasVault QR code. Please scan a QR code generated by AliasVault.",
"cameraPermissionTitle": "Camera Permission Required",
"cameraPermissionMessage": "Please allow camera access to scan QR codes.",
"cameraPermissionMessage": "Veuillez autoriser l'accès à l'appareil photo pour scanner les QR codes.",
"mobileLogin": {
"confirmTitle": "Confirm Login Request",
"confirmSubtitle": "Re-authenticate to approve login on another device.",
@@ -493,7 +493,7 @@
"status": {
"unlockingVault": "Unlocking vault",
"decryptingVault": "Decrypting vault",
"openingVaultReadOnly": "Opening vault in read-only mode",
"openingVaultReadOnly": "Ouvrir le coffre en mode lecture seule",
"retryingConnection": "Retrying connection..."
},
"offline": {

View File

@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 70;
objectVersion = 60;
objects = {
/* Begin PBXBuildFile section */
@@ -212,7 +212,7 @@
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
CEE9098F2DA548C7008D568F /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
CEE9098F2DA548C7008D568F /* Exceptions for "Autofill" folder in "Autofill" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
@@ -222,13 +222,84 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
CE59C7602E4F47FD0024A246 /* VaultUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultUITests; sourceTree = "<group>"; };
CE77825E2EA1822400A75E6F /* VaultUtils */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultUtils; sourceTree = "<group>"; };
CEE480882DBE86DC00F4A367 /* VaultStoreKit */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultStoreKit; sourceTree = "<group>"; };
CEE480972DBE86DD00F4A367 /* VaultStoreKitTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultStoreKitTests; sourceTree = "<group>"; };
CEE4816B2DBE8AC800F4A367 /* VaultUI */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultUI; sourceTree = "<group>"; };
CEE482AB2DBE8EFE00F4A367 /* VaultModels */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = VaultModels; sourceTree = "<group>"; };
CEE909812DA548C7008D568F /* Autofill */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CEE9098F2DA548C7008D568F /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = Autofill; sourceTree = "<group>"; };
CE59C7602E4F47FD0024A246 /* VaultUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = VaultUITests;
sourceTree = "<group>";
};
CE77825E2EA1822400A75E6F /* VaultUtils */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = VaultUtils;
sourceTree = "<group>";
};
CEE480882DBE86DC00F4A367 /* VaultStoreKit */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = VaultStoreKit;
sourceTree = "<group>";
};
CEE480972DBE86DD00F4A367 /* VaultStoreKitTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = VaultStoreKitTests;
sourceTree = "<group>";
};
CEE4816B2DBE8AC800F4A367 /* VaultUI */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = VaultUI;
sourceTree = "<group>";
};
CEE482AB2DBE8EFE00F4A367 /* VaultModels */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
);
explicitFileTypes = {
};
explicitFolders = (
);
path = VaultModels;
sourceTree = "<group>";
};
CEE909812DA548C7008D568F /* Autofill */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
CEE9098F2DA548C7008D568F /* Exceptions for "Autofill" folder in "Autofill" target */,
);
explicitFileTypes = {
};
explicitFolders = (
);
path = Autofill;
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
@@ -1349,7 +1420,10 @@
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
OTHER_LDFLAGS = "$(inherited) ";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
@@ -1403,7 +1477,10 @@
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\"";
MTL_ENABLE_DEBUG_INFO = NO;
OTHER_LDFLAGS = "$(inherited) ";
OTHER_LDFLAGS = (
"$(inherited)",
" ",
);
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
SDKROOT = iphoneos;
USE_HERMES = true;

View File

@@ -333,9 +333,9 @@ PODS:
- FBLazyVector (0.79.6)
- fmt (11.0.2)
- glog (0.3.5)
- hermes-engine (0.79.5):
- hermes-engine/Pre-built (= 0.79.5)
- hermes-engine/Pre-built (0.79.5)
- hermes-engine (0.79.6):
- hermes-engine/Pre-built (= 0.79.6)
- hermes-engine/Pre-built (0.79.6)
- Macaw (0.9.10):
- SWXMLHash
- OpenSSL-Universal (3.3.3001)
@@ -2442,7 +2442,7 @@ PODS:
- SQLite.swift (0.14.1):
- SQLite.swift/standard (= 0.14.1)
- SQLite.swift/standard (0.14.1)
- SwiftLint (0.59.1)
- SwiftLint (0.62.2)
- SWXMLHash (7.0.2)
- Yoga (0.0.0)
@@ -2795,8 +2795,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
boost: 1dca942403ed9342f98334bf4c3621f011aa7946
DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
EXConstants: 98bcf0f22b820f9b28f9fee55ff2daededadd2f8
EXJSONUtils: 1d3e4590438c3ee593684186007028a14b3686cd
EXManifests: 691a779b04e4f2c96da46fb9bef4f86174fefcb5
@@ -2826,9 +2826,9 @@ SPEC CHECKSUMS:
EXUpdatesInterface: 7ff005b7af94ee63fa452ea7bb95d7a8ff40277a
fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6
FBLazyVector: 07309209b7b914451b8f822544a18e2a0a85afff
fmt: 01b82d4ca6470831d1cc0852a1af644be019e8f6
glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a
hermes-engine: f03b0e06d3882d71e67e45b073bb827da1a21aae
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
hermes-engine: 44bb6fe76a6eb400d3a992e2d0b21946ae999fa9
Macaw: 7af8ea57aa2cab35b4a52a45e6f85eea753ea9ae
OpenSSL-Universal: 6082b0bf950e5636fe0d78def171184e2b3899c2
RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82
@@ -2908,7 +2908,7 @@ SPEC CHECKSUMS:
SignalArgon2: 1c24183835ca861e6af06631c18b1671cdf35571
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
SQLite.swift: 2992550ebf3c5b268bf4352603e3df87d2a4ed72
SwiftLint: 3d48e2fb2a3468fdaccf049e5e755df22fb40c2c
SwiftLint: f84fc7d844e9cde0dc4f5013af608a269e317aba
SWXMLHash: dd733a457e9c4fe93b1538654057aefae4acb382
Yoga: dc7c21200195acacb62fa920c588e7c2106de45e

View File

@@ -6416,24 +6416,24 @@
}
},
"node_modules/body-parser": {
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"version": "1.20.4",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"bytes": "~3.1.2",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.13.0",
"raw-body": "2.5.2",
"destroy": "~1.2.0",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"on-finished": "~2.4.1",
"qs": "~6.14.0",
"raw-body": "~2.5.3",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8",
@@ -6450,6 +6450,27 @@
"ms": "2.0.0"
}
},
"node_modules/body-parser/node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/body-parser/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -6457,6 +6478,16 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/body-parser/node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -14354,13 +14385,13 @@
}
},
"node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"version": "6.14.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
"devOptional": true,
"license": "BSD-3-Clause",
"dependencies": {
"side-channel": "^1.0.6"
"side-channel": "^1.1.0"
},
"engines": {
"node": ">=0.6"
@@ -14444,21 +14475,52 @@
}
},
"node_modules/raw-body": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
"bytes": "~3.1.2",
"http-errors": "~2.0.1",
"iconv-lite": "~0.4.24",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/raw-body/node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/raw-body/node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",

View File

@@ -1,7 +1,7 @@
{
"loading": {
"title": "AliasVault wordt geladen",
"message": "De beveiligde omgeving wordt opgestart. AliasVault draait volledig in je browser, waardoor het laden de eerste keer iets langer kan duren.",
"message": "De beveiligde omgeving wordt gestart. AliasVault draait volledig in je browser, waardoor het laden de eerste keer iets langer kan duren.",
"refreshText": "Als het laden vastloopt, kunt je op de onderstaande knop klikken om de pagina te vernieuwen.",
"refreshButtonText": "Vernieuwen"
},

View File

@@ -275,7 +275,7 @@ GEM
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (1.8.0)
uri (1.0.3)
uri (1.0.4)
webrick (1.9.0)
PLATFORMS

View File

@@ -0,0 +1,61 @@
---
layout: default
title: Git versioning strategy
parent: Release
grand_parent: Development
nav_order: 3
---
# Git versioning strategy
This document describes the **official release workflow** for AliasVault.
## Branch Semantics
### `main`
- Represents the **next version line**
- Contains **only pre-release versions**
- Example versions:
- `0.26.0-alpha`
- `0.26.0-beta`
- Never tagged for stable releases
---
### `XXXX-*` (GitHub issue branches)
- Branch from:
- `main` for next-version development, or
- a release tag for hotfixes
- Contains **only code fixes**
- No version changes
- No release notes
- May contain many commits
Landing rules:
- If branched from `main`: merge or rebase back into `main`
- If branched from a tag: **cherry-pick fixes into `main`**
- May be merged into a `release/*` branch for packaging
---
### `release/*`
- Used only to **package a stable release**
- Contains:
- fixes (cherry-picked back into main)
- release notes (cherry-picked back into main)
- version bump
- Never merged into `main`
- Deleted after tagging
---
## Versioning Rules
### Development versions
- Live only on `main`
- Always pre-release (`-alpha`, `-beta`, etc.)
### Stable versions
- Live only on `release/*` branches
- Always tagged
- Never merged back into `main`

View File

@@ -0,0 +1,65 @@
---
layout: default
title: Release Checklist
parent: Release
grand_parent: Development
nav_order: 4
---
# Release Checklist
Step-by-step guide for creating a new AliasVault release.
## 1. Create release branch
- **Feature release**: Branch from `main`
```bash
# Feature release
git checkout main && git checkout -b release/X.Y.Z
```
- **Bug/hotfix release**: Branch from previous tag (e.g., `0.25.2`)
```bash
# Hotfix release
git checkout 0.25.2 && git checkout -b release/0.25.3
```
## 2. Bump version and write release notes
Run the version bump script which automatically bumps all versions and creates Fastlane changelog files:
```bash
./scripts/bump-version.sh
```
- Commit the release notes in its own commit first
- **Cherry-pick the release notes commit to `main`**
- Commit the version bump changes in a separate commit
- The version bump commit stays only in the release branch
- ***Not cherry-picked***, as the `main` branch is always targeting the next feature (pre)release
## 3. Additional changes (optional)
- If additional fixes are needed after testing, add them to the release branch
- **Cherry-pick all fix commits back to `main`**
## 4. Publish release
- Create the release from GitHub Releases based on the release branch
- Tag is created automatically
## 5. Verify cherry-picks
After release, verify all relevant changes were cherry-picked to `main`:
```bash
git range-diff <previous-tag>..release/<version> <previous-tag>..main
# Example:
git range-diff 0.25.2..release/0.25.3 0.25.2..main
```
**Expected output:**
- Only the version bump commit should show as `<` (only in release branch)
- All other commits should show as `=` (in both branches)

View File

@@ -0,0 +1,2 @@
- Android Passkey compatibility improvements
- Add French and Spanish language options

View File

@@ -0,0 +1,2 @@
- Android Passkey compatibiliteit verbeterd
- Frans en Spaans toegevoegd als taalopties

View File

@@ -0,0 +1 @@
- Add French and Spanish language options

View File

@@ -0,0 +1 @@
- Add French and Spanish language options

View File

@@ -0,0 +1 @@
- Frans en Spaans toegevoegd als taalopties

View File

@@ -0,0 +1,80 @@
#!/bin/bash
#
# Show the latest changelogs for all platforms (Android, iOS, Browser Extension)
# Outputs formatted changelog content for easy copy-paste
#
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
METADATA_DIR="$ROOT_DIR/fastlane/metadata"
# Function to get the latest changelog file from a directory
get_latest_changelog() {
local dir="$1"
if [ -d "$dir" ]; then
# Sort files by version number (handles both numeric and semver formats)
ls -1 "$dir" 2>/dev/null | sort -V | tail -1
fi
}
# Function to print Android changelogs (XML format, no spaces between languages)
print_android() {
local changelog_dir="$METADATA_DIR/android/en-US/changelogs"
local latest_file=$(get_latest_changelog "$changelog_dir")
echo "================================================================================"
echo "ANDROID (latest: $latest_file)"
echo "================================================================================"
echo ""
# Print all locales in XML format without blank lines between them
for locale_dir in "$METADATA_DIR/android"/*; do
if [ -d "$locale_dir/changelogs" ]; then
locale=$(basename "$locale_dir")
local file="$locale_dir/changelogs/$latest_file"
if [ -f "$file" ]; then
echo "<$locale>"
cat "$file"
echo "</$locale>"
fi
fi
done
echo ""
}
# Function to print iOS/Browser Extension changelogs (each language separate)
print_simple() {
local platform="$1"
local display_name="$2"
local changelog_dir="$METADATA_DIR/$platform/en-US/changelogs"
local latest_file=$(get_latest_changelog "$changelog_dir")
echo "================================================================================"
echo "$display_name (latest: $latest_file)"
echo "================================================================================"
# Print each locale separately
for locale_dir in "$METADATA_DIR/$platform"/*; do
if [ -d "$locale_dir/changelogs" ]; then
locale=$(basename "$locale_dir")
local file="$locale_dir/changelogs/$latest_file"
if [ -f "$file" ]; then
echo ""
echo "--- $locale ---"
cat "$file"
fi
fi
done
echo ""
}
echo ""
echo "Latest Changelogs Summary"
echo ""
print_android
print_simple "ios" "iOS"
print_simple "browser-extension" "BROWSER EXTENSION"
echo "================================================================================"
echo ""