Merge branch 'molly-7.0'

This commit is contained in:
Oscar Mira 2024-03-05 19:50:02 +01:00
commit d470ac9500
106 changed files with 606 additions and 215 deletions

View file

@ -19,8 +19,8 @@ apply {
from("fix-profm.gradle")
}
val canonicalVersionCode = 1395
val canonicalVersionName = "7.0.1"
val canonicalVersionCode = 1396
val canonicalVersionName = "7.0.2"
val mollyRevision = 1
val postFixSize = 100

View file

@ -788,6 +788,17 @@ class RecipientTableTest_getAndPossiblyMerge {
expectChangeNumberEvent()
}
test("merge, e164 follows pni+aci") {
given(E164_A, PNI_A, null)
given(null, null, ACI_A)
process(null, PNI_A, ACI_A, pniVerified = true)
expect(E164_A, PNI_A, ACI_A)
expectThreadMergeEvent(E164_A)
expectPniVerified()
}
test("local user, local e164 and aci provided, changeSelf=false, leave e164 alone") {
given(E164_SELF, null, ACI_SELF)
given(null, null, ACI_A)

View file

@ -40,7 +40,7 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK
ApplicationDependencies.getIncomingMessageObserver()
.processEnvelope(bufferedStore, envelope, serverDeliveredTimestamp)
?.mapNotNull { it.run() }
?.forEach { ApplicationDependencies.getJobManager().add(it) }
?.forEach { it.enqueue() }
bufferedStore.flushToDisk()
val end = System.currentTimeMillis()

View file

@ -109,8 +109,6 @@ class AppSettingsActivity : DSLSettingsActivity() {
override fun onWillFinish() {
if (wasConfigurationUpdated) {
setResult(MainActivity.RESULT_CONFIG_CHANGED)
} else {
setResult(RESULT_OK)
}
}

View file

@ -174,6 +174,48 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
}
)
clickPref(
title = DSLSettingsText.from("Log dump PreKey ServiceId-KeyIds"),
onClick = {
logPreKeyIds()
}
)
clickPref(
title = DSLSettingsText.from("Retry all jobs now"),
summary = DSLSettingsText.from("Clear backoff intervals, app will restart"),
onClick = {
SimpleTask.run({
JobDatabase.getInstance(ApplicationDependencies.getApplication()).debugResetBackoffInterval()
}) {
AppUtil.restart(requireContext())
}
}
)
clickPref(
title = DSLSettingsText.from("Delete all prekeys"),
summary = DSLSettingsText.from("Deletes all signed/last-resort/one-time prekeys for both ACI and PNI accounts. WILL cause problems."),
onClick = {
MaterialAlertDialogBuilder(requireContext())
.setTitle("Delete all prekeys?")
.setMessage("Are you sure? This will delete all prekeys for both ACI and PNI accounts. This WILL cause problems.")
.setPositiveButton(android.R.string.ok) { _, _ ->
SignalDatabase.signedPreKeys.debugDeleteAll()
SignalDatabase.oneTimePreKeys.debugDeleteAll()
SignalDatabase.kyberPreKeys.debugDeleteAll()
Toast.makeText(requireContext(), "All prekeys deleted!", Toast.LENGTH_SHORT).show()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
)
dividerPref()
sectionHeaderPref(DSLSettingsText.from("Logging"))
clickPref(
title = DSLSettingsText.from("Clear all logs"),
onClick = {
@ -215,21 +257,10 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
)
clickPref(
title = DSLSettingsText.from("Log dump PreKey ServiceId-KeyIds"),
title = DSLSettingsText.from("Clear local metrics"),
summary = DSLSettingsText.from("Click to clear all local metrics state."),
onClick = {
logPreKeyIds()
}
)
clickPref(
title = DSLSettingsText.from("Retry all jobs now"),
summary = DSLSettingsText.from("Clear backoff intervals, app will restart"),
onClick = {
SimpleTask.run({
JobDatabase.getInstance(ApplicationDependencies.getApplication()).debugResetBackoffInterval()
}) {
AppUtil.restart(requireContext())
}
clearAllLocalMetricsState()
}
)
@ -424,18 +455,6 @@ class InternalSettingsFragment : DSLSettingsFragment(R.string.preferences__inter
dividerPref()
sectionHeaderPref(DSLSettingsText.from("Local Metrics"))
clickPref(
title = DSLSettingsText.from("Clear local metrics"),
summary = DSLSettingsText.from("Click to clear all local metrics state."),
onClick = {
clearAllLocalMetricsState()
}
)
dividerPref()
sectionHeaderPref(DSLSettingsText.from("Group call server"))
radioPref(

View file

@ -304,7 +304,7 @@ class UsernameLinkSettingsViewModel : ViewModel() {
}
// Draw the username
val usernamePaint = Paint().apply {
val usernamePaint = TextPaint().apply {
color = state.qrCodeColorScheme.textColor.toArgb()
textSize = usernameTextSize
typeface = if (Build.VERSION.SDK_INT < 26) {
@ -316,10 +316,18 @@ class UsernameLinkSettingsViewModel : ViewModel() {
.build()
}
}
val usernameBounds = Rect()
usernamePaint.getTextBounds(state.username, 0, state.username.length, usernameBounds)
androidCanvas.drawText(state.username, (width / 2f) - (usernameBounds.width() / 2f), usernameVerticalPad + usernameBounds.height(), usernamePaint)
val usernameMaxWidth = qrBorderWidth - borderSizeX * 2f
val usernameLayout = StaticLayout(state.username, usernamePaint, usernameMaxWidth.toInt(), Layout.Alignment.ALIGN_CENTER, 1f, 0f, true)
val usernameVerticalOffset = when (usernameLayout.lineCount) {
1 -> 0f
2 -> usernameTextSize / 2f
else -> usernameTextSize
}
androidCanvas.withTranslation(x = backgroundPadHorizontal + borderSizeX, y = usernameVerticalPad - usernameVerticalOffset) {
usernameLayout.draw(this)
}
// Draw the help text
val helpTextPaint = TextPaint().apply {

View file

@ -9,16 +9,15 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@ -46,13 +45,17 @@ fun UsernameQrScanScreen(
onQrResultHandled: () -> Unit,
modifier: Modifier = Modifier
) {
val path = remember { Path() }
when (qrScanResult) {
QrScanResult.InvalidData -> {
QrScanResultDialog(stringResource(R.string.UsernameLinkSettings_qr_result_invalid), onDismiss = onQrResultHandled)
}
QrScanResult.NetworkError -> {
QrScanResultDialog(stringResource(R.string.UsernameLinkSettings_qr_result_network_error), onDismiss = onQrResultHandled)
}
is QrScanResult.NotFound -> {
if (qrScanResult.username != null) {
QrScanResultDialog(stringResource(R.string.UsernameLinkSettings_qr_result_not_found, qrScanResult.username), onDismiss = onQrResultHandled)
@ -60,10 +63,12 @@ fun UsernameQrScanScreen(
QrScanResultDialog(stringResource(R.string.UsernameLinkSettings_qr_result_not_found_no_username), onDismiss = onQrResultHandled)
}
}
is QrScanResult.Success -> {
CommunicationActions.startConversation(LocalContext.current, qrScanResult.recipient, null)
onQrResultHandled()
}
null -> {}
}
@ -88,7 +93,7 @@ fun UsernameQrScanScreen(
.weight(1f, true)
.drawWithContent {
drawContent()
drawQrCrosshair()
drawQrCrosshair(path)
}
)
@ -117,33 +122,39 @@ private fun QrScanResultDialog(message: String, onDismiss: () -> Unit) {
)
}
private fun DrawScope.drawQrCrosshair() {
private fun DrawScope.drawQrCrosshair(path: Path) {
val crosshairWidth: Float = size.minDimension * 0.6f
val clearWidth: Float = crosshairWidth * 0.75f
val crosshairLineLength = crosshairWidth * 0.125f
// Draw a full white rounded rect...
drawRoundRect(
color = Color.White,
topLeft = center - Offset(crosshairWidth / 2, crosshairWidth / 2),
style = Stroke(width = 3.dp.toPx()),
size = Size(crosshairWidth, crosshairWidth),
cornerRadius = CornerRadius(10.dp.toPx(), 10.dp.toPx())
)
val topLeft = center - Offset(crosshairWidth / 2, crosshairWidth / 2)
val topRight = center + Offset(crosshairWidth / 2, -crosshairWidth / 2)
val bottomRight = center + Offset(crosshairWidth / 2, crosshairWidth / 2)
val bottomLeft = center + Offset(-crosshairWidth / 2, crosshairWidth / 2)
// ...then cut out the middle parts with BlendMode.Clear to leave us with just the corners
drawRect(
color = Color.White,
topLeft = Offset(center.x - clearWidth / 2, 0f),
style = Fill,
size = Size(clearWidth, size.height),
blendMode = BlendMode.Clear
)
path.reset()
drawRect(
drawPath(
path = path.apply {
moveTo(topLeft.x, topLeft.y + crosshairLineLength)
lineTo(topLeft.x, topLeft.y)
lineTo(topLeft.x + crosshairLineLength, topLeft.y)
moveTo(topRight.x - crosshairLineLength, topRight.y)
lineTo(topRight.x, topRight.y)
lineTo(topRight.x, topRight.y + crosshairLineLength)
moveTo(bottomRight.x, bottomRight.y - crosshairLineLength)
lineTo(bottomRight.x, bottomRight.y)
lineTo(bottomRight.x - crosshairLineLength, bottomRight.y)
moveTo(bottomLeft.x + crosshairLineLength, bottomLeft.y)
lineTo(bottomLeft.x, bottomLeft.y)
lineTo(bottomLeft.x, bottomLeft.y - crosshairLineLength)
},
color = Color.White,
topLeft = Offset(0f, center.y - clearWidth / 2),
style = Fill,
size = Size(size.width, clearWidth),
blendMode = BlendMode.Clear
style = Stroke(
width = 3.dp.toPx(),
pathEffect = PathEffect.cornerPathEffect(10.dp.toPx())
)
)
}

View file

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.database
import android.content.Context
import org.signal.core.util.delete
import org.signal.core.util.deleteAll
import org.signal.core.util.exists
import org.signal.core.util.insertInto
import org.signal.core.util.logging.Log
@ -171,6 +172,10 @@ class KyberPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Datab
Log.i(TAG, "Deleted $count stale one-time EC prekeys.")
}
fun debugDeleteAll() {
writableDatabase.deleteAll(OneTimePreKeyTable.TABLE_NAME)
}
data class KyberPreKey(
val record: KyberPreKeyRecord,
val lastResort: Boolean

View file

@ -5,6 +5,7 @@ import androidx.core.content.contentValuesOf
import org.signal.core.util.Base64
import org.signal.core.util.SqlUtil
import org.signal.core.util.delete
import org.signal.core.util.deleteAll
import org.signal.core.util.logging.Log
import org.signal.core.util.requireNonNullString
import org.signal.core.util.update
@ -115,6 +116,10 @@ class OneTimePreKeyTable(context: Context, databaseHelper: SignalDatabase) : Dat
Log.i(TAG, "Deleted $count stale one-time EC prekeys.")
}
fun debugDeleteAll() {
writableDatabase.deleteAll(TABLE_NAME)
}
private fun ServiceId.toAccountId(): String {
return when (this) {
is ServiceId.ACI -> this.toString()

View file

@ -2942,10 +2942,10 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
primaryId = data.byAci,
secondaryId = data.byPni
)
} else if (data.pniRecord.aci == null && data.pniRecord.e164 == data.e164) {
} else if (data.pniRecord.aci == null && (data.e164 == null || data.pniRecord.e164 == data.e164)) {
// The PNI record also has the E164 on it with no ACI. We're going to be stealing all of it's fields,
// so this is basically a merge with a little bit of extra prep.
breadCrumbs += "PniRecordHasMatchingE164AndNoAci"
breadCrumbs += "PniRecordHasNoAci"
if (data.aciRecord.pni != null) {
operations += PnpOperation.RemovePni(data.byAci)
@ -2970,10 +2970,10 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
secondaryId = data.byPni
)
} else {
// The PNI record either has an ACI or a non-matching e164, meaning we need to steal what we need and leave the rest behind
// The PNI record has a different ACI, meaning we need to steal what we need and leave the rest behind
breadCrumbs += if (data.pniRecord.aci != null && data.pniRecord.e164 != data.e164) {
"PniRecordHasAciAndNonMatchingE164"
"PniRecordHasAci"
} else if (data.pniRecord.aci != null) {
"PniRecordHasAci"
} else {
@ -4054,7 +4054,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
PROFILE_FAMILY_NAME to null,
PROFILE_JOINED_NAME to null,
LAST_PROFILE_FETCH to 0,
PROFILE_AVATAR to null
PROFILE_AVATAR to null,
PROFILE_SHARING to 0
)
.run {
if (recipientId == null) {

View file

@ -4,6 +4,7 @@ import android.content.Context
import androidx.core.content.contentValuesOf
import org.signal.core.util.Base64
import org.signal.core.util.SqlUtil
import org.signal.core.util.deleteAll
import org.signal.core.util.logging.Log
import org.signal.core.util.requireInt
import org.signal.core.util.requireLong
@ -103,6 +104,10 @@ class SignedPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Data
writableDatabase.delete(TABLE_NAME, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(serviceId.toAccountId(), keyId))
}
fun debugDeleteAll() {
writableDatabase.deleteAll(OneTimePreKeyTable.TABLE_NAME)
}
private fun ServiceId.toAccountId(): String {
return when (this) {
is ServiceId.ACI -> this.toString()

View file

@ -1541,7 +1541,7 @@ final class GroupsV2UpdateMessageProducer {
String beforeChunk = template.substring(startIndex, nearestPosition);
builder.append(beforeChunk);
builder.append(SpanUtil.clickable(Recipient.resolved(recipientId).getDisplayName(context), ContextCompat.getColor(context, R.color.conversation_item_update_text_color), v -> {
builder.append(SpanUtil.clickable(Recipient.resolved(recipientId).getDisplayNameOrUsername(context), ContextCompat.getColor(context, R.color.conversation_item_update_text_color), v -> {
if (!recipientId.isUnknown() && clickHandler != null) {
clickHandler.accept(recipientId);
}

View file

@ -76,6 +76,15 @@ class JobController {
notifyAll();
}
@WorkerThread
void submitNewJobChains(@NonNull List<List<List<Job>>> chains) {
synchronized (this) {
for (List<List<Job>> chain : chains) {
submitNewJobChain(chain);
}
}
}
@WorkerThread
void submitNewJobChain(@NonNull List<List<Job>> chain) {
synchronized (this) {

View file

@ -36,6 +36,7 @@ import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* Allows the scheduling of durable jobs that will be run as early as possible.
@ -225,6 +226,24 @@ public class JobManager implements ConstraintObserver.Notifier {
});
}
public void addAllChains(@NonNull List<JobManager.Chain> chains) {
if (chains.isEmpty()) {
return;
}
for (Chain chain : chains) {
for (List<Job> jobList : chain.getJobListChain()) {
for (Job job : jobList) {
jobTracker.onStateChange(job, JobTracker.JobState.PENDING);
}
}
}
runOnExecutor(() -> {
jobController.submitNewJobChains(chains.stream().map(Chain::getJobListChain).collect(Collectors.toList()));
});
}
/**
* Begins the creation of a job chain with a single job.
* @see Chain

View file

@ -98,10 +98,11 @@ class PnpInitializeDevicesJob private constructor(parameters: Parameters) : Base
try {
Log.i(TAG, "Initializing PNI for linked devices")
initializeDevices(e164)
val result: VerifyResponseWithoutKbs = initializeDevices(e164)
.map(::VerifyResponseWithoutKbs)
.safeBlockingGet()
.resultOrThrow
result.error?.let { throw it }
} catch (e: InterruptedException) {
throw IOException("Retry", e)
} catch (t: Throwable) {

View file

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.jobs
import androidx.annotation.VisibleForTesting
import org.signal.core.util.logging.Log
import org.signal.core.util.roundedString
import org.signal.libsignal.protocol.state.KyberPreKeyRecord
import org.signal.libsignal.protocol.state.PreKeyRecord
import org.signal.libsignal.protocol.state.SignalProtocolStore
@ -11,7 +12,9 @@ import org.thoughtcrime.securesms.crypto.storage.PreKeyMetadataStore
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
import org.thoughtcrime.securesms.jobs.protos.PreKeysSyncJobData
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.FeatureFlags
import org.whispersystems.signalservice.api.SignalServiceAccountDataStore
import org.whispersystems.signalservice.api.account.PreKeyUpload
import org.whispersystems.signalservice.api.push.ServiceId
@ -34,7 +37,10 @@ import kotlin.time.DurationUnit
* It will also rotate/create last-resort kyber prekeys for both ACI and PNI identities, as well as ensure
* that the user has a sufficient number of one-time kyber prekeys available on the service.
*/
class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(parameters) {
class PreKeysSyncJob private constructor(
parameters: Parameters,
private val forceRotationRequested: Boolean
) : BaseJob(parameters) {
companion object {
const val KEY = "PreKeysSyncJob"
@ -52,9 +58,13 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param
@JvmField
val MAXIMUM_ALLOWED_SIGNED_PREKEY_AGE = 14.days.inWholeMilliseconds
/**
* @param forceRotationRequested If true, this will force the rotation of all keys, provided we haven't already done a forced refresh recently.
*/
@JvmOverloads
@JvmStatic
fun create(): PreKeysSyncJob {
return PreKeysSyncJob()
fun create(forceRotationRequested: Boolean = false): PreKeysSyncJob {
return PreKeysSyncJob(forceRotationRequested)
}
@JvmStatic
@ -87,19 +97,22 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
constructor() : this(
constructor(forceRotation: Boolean = false) : this(
Parameters.Builder()
.setQueue("PreKeysSyncJob")
.addConstraint(NetworkConstraint.KEY)
.setMaxInstancesForFactory(1)
.setMaxAttempts(Parameters.UNLIMITED)
.setLifespan(TimeUnit.DAYS.toMillis(30))
.build()
.build(),
forceRotation
)
override fun getFactoryKey(): String = KEY
override fun serialize(): ByteArray? = null
override fun serialize(): ByteArray {
return PreKeysSyncJobData(forceRotationRequested).encode()
}
override fun onRun() {
if (!SignalStore.account().isRegistered || SignalStore.account().aci == null || SignalStore.account().pni == null) {
@ -107,12 +120,30 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param
return
}
syncPreKeys(ServiceIdType.ACI, SignalStore.account().aci, ApplicationDependencies.getProtocolStore().aci(), SignalStore.account().aciPreKeys)
syncPreKeys(ServiceIdType.PNI, SignalStore.account().pni, ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys)
val forceRotation = if (forceRotationRequested) {
val timeSinceLastForcedRotation = System.currentTimeMillis() - SignalStore.misc().lastForcedPreKeyRefresh
// We check < 0 in case someone changed their clock and had a bad value set
timeSinceLastForcedRotation > FeatureFlags.preKeyForceRefreshInterval() || timeSinceLastForcedRotation < 0
} else {
false
}
if (forceRotation) {
warn(TAG, "Forcing prekey rotation.")
} else if (forceRotationRequested) {
warn(TAG, "Forced prekey rotation was requested, but we already did a forced refresh ${System.currentTimeMillis() - SignalStore.misc().lastForcedPreKeyRefresh} ms ago. Ignoring.")
}
syncPreKeys(ServiceIdType.ACI, SignalStore.account().aci, ApplicationDependencies.getProtocolStore().aci(), SignalStore.account().aciPreKeys, forceRotation)
syncPreKeys(ServiceIdType.PNI, SignalStore.account().pni, ApplicationDependencies.getProtocolStore().pni(), SignalStore.account().pniPreKeys, forceRotation)
SignalStore.misc().lastFullPrekeyRefreshTime = System.currentTimeMillis()
if (forceRotation) {
SignalStore.misc().lastForcedPreKeyRefresh = System.currentTimeMillis()
}
}
private fun syncPreKeys(serviceIdType: ServiceIdType, serviceId: ServiceId?, protocolStore: SignalServiceAccountDataStore, metadataStore: PreKeyMetadataStore) {
private fun syncPreKeys(serviceIdType: ServiceIdType, serviceId: ServiceId?, protocolStore: SignalServiceAccountDataStore, metadataStore: PreKeyMetadataStore, forceRotation: Boolean) {
if (serviceId == null) {
warn(TAG, serviceIdType, "AccountId not set!")
return
@ -121,20 +152,20 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param
val accountManager = ApplicationDependencies.getSignalServiceAccountManager()
val availablePreKeyCounts: OneTimePreKeyCounts = accountManager.getPreKeyCounts(serviceIdType)
val signedPreKeyToUpload: SignedPreKeyRecord? = signedPreKeyUploadIfNeeded(serviceIdType, protocolStore, metadataStore)
val signedPreKeyToUpload: SignedPreKeyRecord? = signedPreKeyUploadIfNeeded(serviceIdType, protocolStore, metadataStore, forceRotation)
val oneTimeEcPreKeysToUpload: List<PreKeyRecord>? = if (availablePreKeyCounts.ecCount < ONE_TIME_PREKEY_MINIMUM) {
log(serviceIdType, "There are ${availablePreKeyCounts.ecCount} one-time EC prekeys available, which is less than our threshold. Need more.")
val oneTimeEcPreKeysToUpload: List<PreKeyRecord>? = if (forceRotation || availablePreKeyCounts.ecCount < ONE_TIME_PREKEY_MINIMUM) {
log(serviceIdType, "There are ${availablePreKeyCounts.ecCount} one-time EC prekeys available, which is less than our threshold. Need more. (Forced: $forceRotation)")
PreKeyUtil.generateAndStoreOneTimeEcPreKeys(protocolStore, metadataStore)
} else {
log(serviceIdType, "There are ${availablePreKeyCounts.ecCount} one-time EC prekeys available, which is enough.")
null
}
val lastResortKyberPreKeyToUpload: KyberPreKeyRecord? = lastResortKyberPreKeyUploadIfNeeded(serviceIdType, protocolStore, metadataStore)
val lastResortKyberPreKeyToUpload: KyberPreKeyRecord? = lastResortKyberPreKeyUploadIfNeeded(serviceIdType, protocolStore, metadataStore, forceRotation)
val oneTimeKyberPreKeysToUpload: List<KyberPreKeyRecord>? = if (availablePreKeyCounts.kyberCount < ONE_TIME_PREKEY_MINIMUM) {
log(serviceIdType, "There are ${availablePreKeyCounts.kyberCount} one-time kyber prekeys available, which is less than our threshold. Need more.")
val oneTimeKyberPreKeysToUpload: List<KyberPreKeyRecord>? = if (forceRotation || availablePreKeyCounts.kyberCount < ONE_TIME_PREKEY_MINIMUM) {
log(serviceIdType, "There are ${availablePreKeyCounts.kyberCount} one-time kyber prekeys available, which is less than our threshold. Need more. (Forced: $forceRotation)")
PreKeyUtil.generateAndStoreOneTimeKyberPreKeys(protocolStore, metadataStore)
} else {
log(serviceIdType, "There are ${availablePreKeyCounts.kyberCount} one-time kyber prekeys available, which is enough.")
@ -183,28 +214,28 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param
PreKeyUtil.cleanOneTimePreKeys(protocolStore)
}
private fun signedPreKeyUploadIfNeeded(serviceIdType: ServiceIdType, protocolStore: SignalProtocolStore, metadataStore: PreKeyMetadataStore): SignedPreKeyRecord? {
private fun signedPreKeyUploadIfNeeded(serviceIdType: ServiceIdType, protocolStore: SignalProtocolStore, metadataStore: PreKeyMetadataStore, forceRotation: Boolean): SignedPreKeyRecord? {
val signedPreKeyRegistered = metadataStore.isSignedPreKeyRegistered && metadataStore.activeSignedPreKeyId >= 0
val timeSinceLastSignedPreKeyRotation = System.currentTimeMillis() - metadataStore.lastSignedPreKeyRotationTime
return if (!signedPreKeyRegistered || timeSinceLastSignedPreKeyRotation >= REFRESH_INTERVAL || timeSinceLastSignedPreKeyRotation < 0) {
log(serviceIdType, "Rotating signed prekey. SignedPreKeyRegistered: $signedPreKeyRegistered, TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)")
return if (forceRotation || !signedPreKeyRegistered || timeSinceLastSignedPreKeyRotation >= REFRESH_INTERVAL || timeSinceLastSignedPreKeyRotation < 0) {
log(serviceIdType, "Rotating signed prekey. ForceRotation: $forceRotation, SignedPreKeyRegistered: $signedPreKeyRegistered, TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS).roundedString(2)} days)")
PreKeyUtil.generateAndStoreSignedPreKey(protocolStore, metadataStore)
} else {
log(serviceIdType, "No need to rotate signed prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)")
log(serviceIdType, "No need to rotate signed prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS).roundedString(2)} days)")
null
}
}
private fun lastResortKyberPreKeyUploadIfNeeded(serviceIdType: ServiceIdType, protocolStore: SignalServiceAccountDataStore, metadataStore: PreKeyMetadataStore): KyberPreKeyRecord? {
private fun lastResortKyberPreKeyUploadIfNeeded(serviceIdType: ServiceIdType, protocolStore: SignalServiceAccountDataStore, metadataStore: PreKeyMetadataStore, forceRotation: Boolean): KyberPreKeyRecord? {
val lastResortRegistered = metadataStore.lastResortKyberPreKeyId >= 0
val timeSinceLastSignedPreKeyRotation = System.currentTimeMillis() - metadataStore.lastResortKyberPreKeyRotationTime
return if (!lastResortRegistered || timeSinceLastSignedPreKeyRotation >= REFRESH_INTERVAL || timeSinceLastSignedPreKeyRotation < 0) {
log(serviceIdType, "Rotating last-resort kyber prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)")
return if (forceRotation || !lastResortRegistered || timeSinceLastSignedPreKeyRotation >= REFRESH_INTERVAL || timeSinceLastSignedPreKeyRotation < 0) {
log(serviceIdType, "Rotating last-resort kyber prekey. ForceRotation: $forceRotation, TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS).roundedString(2)} days)")
PreKeyUtil.generateAndStoreLastResortKyberPreKey(protocolStore, metadataStore)
} else {
log(serviceIdType, "No need to rotate last-resort kyber prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS)} days)")
log(serviceIdType, "No need to rotate last-resort kyber prekey. TimeSinceLastRotation: $timeSinceLastSignedPreKeyRotation ms (${timeSinceLastSignedPreKeyRotation.milliseconds.toDouble(DurationUnit.DAYS).roundedString(2)} days)")
null
}
}
@ -225,7 +256,10 @@ class PreKeysSyncJob private constructor(parameters: Parameters) : BaseJob(param
class Factory : Job.Factory<PreKeysSyncJob> {
override fun create(parameters: Parameters, serializedData: ByteArray?): PreKeysSyncJob {
return PreKeysSyncJob(parameters)
return serializedData?.let {
val data = PreKeysSyncJobData.ADAPTER.decode(serializedData)
PreKeysSyncJob(parameters, data.forceRefreshRequested)
} ?: PreKeysSyncJob(parameters, forceRotationRequested = false)
}
}
}

View file

@ -370,9 +370,12 @@ class RetrieveProfileJob private constructor(parameters: Parameters, private val
}
if (writeChangeEvent || localDisplayName.isEmpty()) {
ApplicationDependencies.getDatabaseObserver().notifyConversationListListeners()
val threadId = SignalDatabase.threads.getThreadIdFor(recipient.id)
if (threadId != null) {
ApplicationDependencies.getMessageNotifier().updateNotification(context, forConversation(threadId))
SignalDatabase.runPostSuccessfulTransaction {
ApplicationDependencies.getMessageNotifier().updateNotification(context, forConversation(threadId))
}
}
}

View file

@ -39,6 +39,7 @@ public final class MiscellaneousValues extends SignalStoreValues {
private static final String SERVER_TIME_OFFSET = "misc.server_time_offset";
private static final String LAST_SERVER_TIME_OFFSET_UPDATE = "misc.last_server_time_offset_update";
private static final String NEEDS_USERNAME_RESTORE = "misc.needs_username_restore";
private static final String LAST_FORCED_PREKEY_REFRESH = "misc.last_forced_prekey_refresh";
MiscellaneousValues(@NonNull KeyValueStore store) {
super(store);
@ -339,4 +340,18 @@ public final class MiscellaneousValues extends SignalStoreValues {
public void setNeedsUsernameRestore(boolean value) {
putBoolean(NEEDS_USERNAME_RESTORE, value);
}
/**
* Set the last time we successfully completed a forced prekey refresh.
*/
public void setLastForcedPreKeyRefresh(long time) {
putLong(LAST_FORCED_PREKEY_REFRESH, time);
}
/**
* Get the last time we successfully completed a forced prekey refresh.
*/
public long getLastForcedPreKeyRefresh() {
return getLong(LAST_FORCED_PREKEY_REFRESH, 0);
}
}

View file

@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.service.KeyCachingService
import org.thoughtcrime.securesms.util.AppForegroundObserver
import org.thoughtcrime.securesms.util.SignalLocalMetrics
import org.thoughtcrime.securesms.util.asChain
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
import org.whispersystems.signalservice.api.websocket.WebSocketUnavailableException
@ -309,7 +310,7 @@ class IncomingMessageObserver(private val context: Application) {
is MessageDecryptor.Result.Success -> {
val job = PushProcessMessageJob.processOrDefer(messageContentProcessor, result, localReceiveMetric)
if (job != null) {
return result.followUpOperations + FollowUpOperation { job }
return result.followUpOperations + FollowUpOperation { job.asChain() }
}
}
is MessageDecryptor.Result.Error -> {
@ -318,7 +319,7 @@ class IncomingMessageObserver(private val context: Application) {
result.toMessageState(),
result.errorMetadata.toExceptionMetadata(),
result.envelope.timestamp!!
)
).asChain()
}
}
is MessageDecryptor.Result.Ignore -> {
@ -419,7 +420,7 @@ class IncomingMessageObserver(private val context: Application) {
if (followUpOperations != null) {
Log.d(TAG, "Running ${followUpOperations.size} follow-up operations...")
val jobs = followUpOperations.mapNotNull { it.run() }
ApplicationDependencies.getJobManager().addAll(jobs)
ApplicationDependencies.getJobManager().addAllChains(jobs)
}
signalWebSocket.sendAck(response)

View file

@ -668,12 +668,16 @@ open class MessageContentProcessor(private val context: Context) {
return
}
if (decryptionErrorMessage.ratchetKey.isPresent &&
ratchetKeyMatches(requester, metadata.sourceDeviceId, decryptionErrorMessage.ratchetKey.get())
) {
warn(envelope.timestamp!!, "[RetryReceipt-I] Ratchet key matches. Archiving the session.")
ApplicationDependencies.getProtocolStore().aci().sessions().archiveSession(requester.requireServiceId(), metadata.sourceDeviceId)
archivedSession = true
if (decryptionErrorMessage.ratchetKey.isPresent) {
if (ratchetKeyMatches(requester, metadata.sourceDeviceId, decryptionErrorMessage.ratchetKey.get())) {
warn(envelope.timestamp!!, "[RetryReceipt-I] Ratchet key matches. Archiving the session.")
ApplicationDependencies.getProtocolStore().aci().sessions().archiveSession(requester.requireServiceId(), metadata.sourceDeviceId)
archivedSession = true
} else {
log(envelope.timestamp!!, "[RetryReceipt-I] Ratchet key does not match. Leaving the session as-is.")
}
} else {
warn(envelope.timestamp!!, "[RetryReceipt-I] Missing ratchet key! Can't archive session.")
}
if (messageLogEntry != null) {

View file

@ -37,7 +37,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.groups.BadGroupIdException
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.JobManager
import org.thoughtcrime.securesms.jobs.AutomaticSessionResetJob
import org.thoughtcrime.securesms.jobs.PreKeysSyncJob
import org.thoughtcrime.securesms.jobs.SendRetryReceiptJob
@ -50,6 +50,8 @@ import org.thoughtcrime.securesms.notifications.NotificationIds
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.util.FeatureFlags
import org.thoughtcrime.securesms.util.LRUCache
import org.thoughtcrime.securesms.util.asChain
import org.whispersystems.signalservice.api.InvalidMessageStructureException
import org.whispersystems.signalservice.api.crypto.ContentHint
import org.whispersystems.signalservice.api.crypto.EnvelopeMetadata
@ -77,6 +79,8 @@ object MessageDecryptor {
private val TAG = Log.tag(MessageDecryptor::class.java)
private val decryptionErrorCounts: MutableMap<RecipientId, DecryptionErrorCount> = LRUCache(100)
/**
* Decrypts an envelope and provides a [Result]. This method has side effects, but all of them are limited to [SignalDatabase].
* That means that this operation should be atomic when performed within a transaction.
@ -125,8 +129,9 @@ object MessageDecryptor {
val followUpOperations: MutableList<FollowUpOperation> = mutableListOf()
if (envelope.type == Envelope.Type.PREKEY_BUNDLE) {
Log.i(TAG, "${logPrefix(envelope)} Prekey message. Scheduling a prekey sync job.")
followUpOperations += FollowUpOperation {
PreKeysSyncJob.create()
PreKeysSyncJob.create().asChain()
}
}
@ -219,7 +224,7 @@ object MessageDecryptor {
followUpOperations += FollowUpOperation {
val sender: Recipient = Recipient.external(context, e.sender)
AutomaticSessionResetJob(sender.id, e.senderDevice, envelope.timestamp!!)
AutomaticSessionResetJob(sender.id, e.senderDevice, envelope.timestamp!!).asChain()
}
Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations.toUnmodifiableList())
@ -277,36 +282,70 @@ object MessageDecryptor {
val senderDevice: Int = protocolException.senderDevice
val receivedTimestamp: Long = System.currentTimeMillis()
val sender: Recipient = Recipient.external(context, protocolException.sender)
val senderServiceId: ServiceId? = ServiceId.parseOrNull(protocolException.sender)
if (sender.isSelf) {
Log.w(TAG, "${logPrefix(envelope)} Decryption error for a sync message! Enqueuing a session reset job.")
Log.w(TAG, "${logPrefix(envelope)} Decryption error for a sync message! Enqueuing a session reset job.", true)
followUpOperations += FollowUpOperation {
AutomaticSessionResetJob(sender.id, senderDevice, envelope.timestamp!!)
AutomaticSessionResetJob(sender.id, senderDevice, envelope.timestamp!!).asChain()
}
return Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations)
}
val errorCount: DecryptionErrorCount = decryptionErrorCounts.getOrPut(sender.id) { DecryptionErrorCount(count = 0, lastReceivedTime = 0) }
val timeSinceLastError = receivedTimestamp - errorCount.lastReceivedTime
if (timeSinceLastError > FeatureFlags.retryReceiptMaxCountResetAge() && errorCount.count > 0) {
Log.i(TAG, "${logPrefix(envelope, senderServiceId)} Resetting decryption error count for ${sender.id} because it has been $timeSinceLastError ms since the last error.", true)
errorCount.count = 0
}
errorCount.count++
errorCount.lastReceivedTime = receivedTimestamp
if (errorCount.count > FeatureFlags.retryReceiptMaxCount()) {
Log.w(TAG, "${logPrefix(envelope, senderServiceId)} This is error number ${errorCount.count} from ${sender.id}, which is greater than the maximum of ${FeatureFlags.retryReceiptMaxCount()}. Ignoring.", true)
if (contentHint == ContentHint.IMPLICIT) {
Log.w(TAG, "${logPrefix(envelope, senderServiceId)} The content hint is $contentHint, so no error message is needed.", true)
Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations)
} else {
Log.w(TAG, "${logPrefix(envelope, senderServiceId)} The content hint is $contentHint, so we need to insert an error right away.", true)
return Result.DecryptionError(envelope, serverDeliveredTimestamp, protocolException.toErrorMetadata(), followUpOperations.toUnmodifiableList())
}
} else {
Log.w(TAG, "${logPrefix(envelope, senderServiceId)} This is error number ${errorCount.count} from ${sender.id}.${if (errorCount.count > 1) " It has been $timeSinceLastError ms since the last error." else "" }", true)
}
followUpOperations += FollowUpOperation {
buildSendRetryReceiptJob(envelope, protocolException, sender)
val retryJob = buildSendRetryReceiptJob(envelope, protocolException, sender)
// Note: if the message is sealed sender, it's envelope type will be UNIDENTIFIED_SENDER. The only way we can currently check if the error is
// prekey-related in that situation is using a string match.
if (envelope.type == Envelope.Type.PREKEY_BUNDLE || protocolException.message?.lowercase()?.contains("prekey") == true) {
Log.w(TAG, "${logPrefix(envelope, senderServiceId)} Got a decryption error on a prekey message. Forcing a prekey rotation before requesting the retry.", true)
PreKeysSyncJob.create(forceRotationRequested = true).asChain().then(retryJob)
} else {
retryJob.asChain()
}
}
return when (contentHint) {
ContentHint.DEFAULT -> {
Log.w(TAG, "${logPrefix(envelope)} The content hint is $contentHint, so we need to insert an error right away.", true)
Log.w(TAG, "${logPrefix(envelope, senderServiceId)} The content hint is $contentHint, so we need to insert an error right away.", true)
Result.DecryptionError(envelope, serverDeliveredTimestamp, protocolException.toErrorMetadata(), followUpOperations.toUnmodifiableList())
}
ContentHint.RESENDABLE -> {
Log.w(TAG, "${logPrefix(envelope)} The content hint is $contentHint, so we can try to resend the message.", true)
Log.w(TAG, "${logPrefix(envelope, senderServiceId)} The content hint is $contentHint, so we can try to resend the message.", true)
followUpOperations += FollowUpOperation {
val groupId: GroupId? = protocolException.parseGroupId(envelope)
val threadId: Long? = if (groupId != null) {
if (SignalDatabase.groups.getGroup(groupId).isAbsent()) {
Log.w(TAG, "${logPrefix(envelope)} No group found for $groupId! Not inserting a retry receipt.")
Log.w(TAG, "${logPrefix(envelope, senderServiceId)} No group found for $groupId! Not inserting a retry receipt.")
return@FollowUpOperation null
}
@ -317,7 +356,7 @@ object MessageDecryptor {
}
if (threadId == null) {
Log.w(TAG, "${logPrefix(envelope)} Thread does not already exist for sender ${sender.id}! We will not create one just to show a retry receipt.")
Log.w(TAG, "${logPrefix(envelope, senderServiceId)} Thread does not already exist for sender ${sender.id}! We will not create one just to show a retry receipt.")
return@FollowUpOperation null
}
@ -330,7 +369,7 @@ object MessageDecryptor {
}
ContentHint.IMPLICIT -> {
Log.w(TAG, "${logPrefix(envelope)} The content hint is $contentHint, so no error message is needed.", true)
Log.w(TAG, "${logPrefix(envelope, senderServiceId)} The content hint is $contentHint, so no error message is needed.", true)
Result.Ignore(envelope, serverDeliveredTimestamp, followUpOperations)
}
}
@ -399,20 +438,24 @@ object MessageDecryptor {
}
private fun logPrefix(envelope: Envelope): String {
return logPrefix(envelope.timestamp!!, envelope.sourceServiceId ?: "<sealed>", envelope.sourceDevice)
return logPrefix(envelope.timestamp!!, ServiceId.parseOrNull(envelope.sourceServiceId)?.logString() ?: "<sealed>", envelope.sourceDevice)
}
private fun logPrefix(envelope: Envelope, sender: ServiceId): String {
return logPrefix(envelope.timestamp!!, sender.toString(), envelope.sourceDevice)
private fun logPrefix(envelope: Envelope, sender: ServiceId?): String {
return logPrefix(envelope.timestamp!!, sender?.logString() ?: "?", envelope.sourceDevice)
}
private fun logPrefix(envelope: Envelope, sender: String): String {
return logPrefix(envelope.timestamp!!, ServiceId.parseOrNull(sender)?.logString() ?: "?", envelope.sourceDevice)
}
private fun logPrefix(envelope: Envelope, cipherResult: SignalServiceCipherResult): String {
return logPrefix(envelope.timestamp!!, cipherResult.metadata.sourceServiceId.toString(), cipherResult.metadata.sourceDeviceId)
return logPrefix(envelope.timestamp!!, cipherResult.metadata.sourceServiceId.logString(), cipherResult.metadata.sourceDeviceId)
}
private fun logPrefix(envelope: Envelope, exception: ProtocolException): String {
return if (exception.sender != null) {
logPrefix(envelope.timestamp!!, exception.sender, exception.senderDevice)
logPrefix(envelope.timestamp!!, ServiceId.parseOrNull(exception.sender)?.logString() ?: "?", exception.senderDevice)
} else {
logPrefix(envelope.timestamp!!, envelope.sourceServiceId, envelope.sourceDevice)
}
@ -546,7 +589,12 @@ object MessageDecryptor {
val groupId: GroupId?
)
data class DecryptionErrorCount(
var count: Int,
var lastReceivedTime: Long
)
fun interface FollowUpOperation {
fun run(): Job?
fun run(): JobManager.Chain?
}
}

View file

@ -141,7 +141,7 @@ public class UsernameEditFragment extends LoggingFragment {
}
private void promptOrSubmitUsername() {
if (args.getMode() == UsernameEditMode.RECOVERY) {
if (viewModel.isSameUsernameRecovery()) {
new MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.UsernameEditFragment_recovery_dialog_confirmation)
.setPositiveButton(android.R.string.ok, ((dialog, which) -> {
@ -317,6 +317,9 @@ public class UsernameEditFragment extends LoggingFragment {
case NETWORK_FAILURE:
Toast.makeText(requireContext(), R.string.UsernameEditFragment_encountered_a_network_error, Toast.LENGTH_SHORT).show();
break;
case RATE_LIMIT_EXCEEDED:
Toast.makeText(requireContext(), R.string.UsernameEditFragment_rate_limit_exceeded_error, Toast.LENGTH_SHORT).show();
break;
case SKIPPED:
closeScreen();
break;

View file

@ -34,9 +34,7 @@ import java.util.concurrent.TimeUnit
* A note on naming conventions:
* Usernames are made up of two discrete components, a nickname and a discriminator. They are formatted thusly:
*
* [nickname].[discriminator]
*
* The nickname is user-controlled, whereas the discriminator is controlled by the server.
* nickname.discriminator
*/
internal class UsernameEditViewModel private constructor(private val mode: UsernameEditMode) : ViewModel() {
private val events: PublishSubject<Event> = PublishSubject.create()
@ -71,6 +69,7 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
if (mode == UsernameEditMode.RECOVERY) {
onNicknameUpdated(SignalStore.account().username?.split(Usernames.DELIMITER)?.first() ?: "")
onDiscriminatorUpdated(SignalStore.account().username?.split(Usernames.DELIMITER)?.last() ?: "")
}
}
@ -129,6 +128,13 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
events.onNext(Event.SKIPPED)
}
fun isSameUsernameRecovery(): Boolean {
val usernameState = uiState.state.usernameState
return mode == UsernameEditMode.RECOVERY &&
usernameState is UsernameState.Reserved &&
usernameState.requireUsername().username.lowercase() == SignalStore.account().username?.lowercase()
}
/**
* @param userConfirmedResetOk True if the user is submitting this after confirming that they're ok with resetting their username via [Event.NEEDS_CONFIRM_RESET].
*/
@ -201,6 +207,11 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
uiState.update { State(ButtonState.SUBMIT, UsernameStatus.NONE, it.usernameState) }
events.onNext(Event.NETWORK_FAILURE)
}
UsernameSetResult.RATE_LIMIT_ERROR -> {
uiState.update { State(ButtonState.SUBMIT, UsernameStatus.NONE, it.usernameState) }
events.onNext(Event.RATE_LIMIT_EXCEEDED)
}
}
}
}
@ -337,6 +348,11 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
events.onNext(Event.NETWORK_FAILURE)
}
UsernameSetResult.RATE_LIMIT_ERROR -> {
uiState.update { State(ButtonState.SUBMIT, UsernameStatus.NONE, UsernameState.NoUsername) }
events.onNext(Event.RATE_LIMIT_EXCEEDED)
}
UsernameSetResult.CANDIDATE_GENERATION_ERROR -> {
// TODO -- Retry
uiState.update { State(ButtonState.SUBMIT_DISABLED, UsernameStatus.TAKEN, UsernameState.NoUsername) }
@ -374,7 +390,7 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
}
enum class Event {
NETWORK_FAILURE, SUBMIT_SUCCESS, DELETE_SUCCESS, SUBMIT_FAIL_INVALID, SUBMIT_FAIL_TAKEN, SKIPPED, NEEDS_CONFIRM_RESET
NETWORK_FAILURE, SUBMIT_SUCCESS, DELETE_SUCCESS, SUBMIT_FAIL_INVALID, SUBMIT_FAIL_TAKEN, SKIPPED, NEEDS_CONFIRM_RESET, RATE_LIMIT_EXCEEDED
}
class Factory(private val mode: UsernameEditMode) : ViewModelProvider.Factory {

View file

@ -23,6 +23,7 @@ import org.whispersystems.signalservice.api.SignalServiceAccountManager
import org.whispersystems.signalservice.api.push.ServiceId.ACI
import org.whispersystems.signalservice.api.push.UsernameLinkComponents
import org.whispersystems.signalservice.api.push.exceptions.NonSuccessfulResponseCodeException
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException
import org.whispersystems.signalservice.api.push.exceptions.UsernameIsNotAssociatedWithAnAccountException
import org.whispersystems.signalservice.api.push.exceptions.UsernameIsNotReservedException
import org.whispersystems.signalservice.api.push.exceptions.UsernameMalformedException
@ -379,6 +380,9 @@ object UsernameRepository {
} catch (e: UsernameMalformedException) {
Log.w(TAG, "[reserveUsername] Username malformed.")
failure(UsernameSetResult.USERNAME_INVALID)
} catch (e: RateLimitException) {
Log.w(TAG, "[reserveUsername] Rate limit exceeded.")
failure(UsernameSetResult.RATE_LIMIT_ERROR)
} catch (e: IOException) {
Log.w(TAG, "[reserveUsername] Generic network exception.", e)
failure(UsernameSetResult.NETWORK_ERROR)
@ -501,7 +505,7 @@ object UsernameRepository {
}
enum class UsernameSetResult {
SUCCESS, USERNAME_UNAVAILABLE, USERNAME_INVALID, NETWORK_ERROR, CANDIDATE_GENERATION_ERROR
SUCCESS, USERNAME_UNAVAILABLE, USERNAME_INVALID, NETWORK_ERROR, CANDIDATE_GENERATION_ERROR, RATE_LIMIT_ERROR
}
enum class UsernameReclaimResult {

View file

@ -653,7 +653,7 @@ public class Recipient {
String name = Util.getFirstNonEmpty(getGroupName(context),
getSystemProfileName().getGivenName(),
getProfileName().getGivenName(),
getE164().orElse(null),
shouldShowE164() ? getE164().orElse(null) : null,
getUsername().orElse(null),
getDisplayName(context));

View file

@ -89,8 +89,8 @@ class AboutSheet : ComposeBottomSheetDialogFragment() {
model = AboutModel(
isSelf = recipient.get().isSelf,
hasAvatar = recipient.get().profileAvatarFileDetails.hasFile(),
displayName = recipient.get().getDisplayName(requireContext()),
shortName = recipient.get().getShortDisplayName(requireContext()),
displayName = recipient.get().getDisplayNameOrUsername(requireContext()),
shortName = recipient.get().getShortDisplayNameIncludingUsername(requireContext()),
about = recipient.get().about,
verified = verified,
recipientForAvatar = recipient.get(),

View file

@ -183,7 +183,7 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
}
String name = recipient.isSelf() ? requireContext().getString(R.string.note_to_self)
: recipient.getDisplayName(requireContext());
: recipient.getDisplayNameOrUsername(requireContext());
fullName.setVisibility(TextUtils.isEmpty(name) ? View.GONE : View.VISIBLE);
SpannableStringBuilder nameBuilder = new SpannableStringBuilder(name);
if (recipient.showVerified()) {

View file

@ -119,6 +119,9 @@ public final class FeatureFlags {
private static final String GIF_SEARCH = "global.gifSearch";
private static final String AUDIO_REMUXING = "android.media.audioRemux.1";
private static final String VIDEO_RECORD_1X_ZOOM = "android.media.videoCaptureDefaultZoom";
private static final String RETRY_RECEIPT_MAX_COUNT = "android.retryReceipt.maxCount";
private static final String RETRY_RECEIPT_MAX_COUNT_RESET_AGE = "android.retryReceipt.maxCountResetAge";
private static final String PREKEY_FORCE_REFRESH_INTERVAL = "android.prekeyForceRefreshInterval";
/**
* We will only store remote values for flags in this set. If you want a flag to be controllable
@ -190,7 +193,10 @@ public final class FeatureFlags {
USE_ACTIVE_CALL_MANAGER,
GIF_SEARCH,
AUDIO_REMUXING,
VIDEO_RECORD_1X_ZOOM
VIDEO_RECORD_1X_ZOOM,
RETRY_RECEIPT_MAX_COUNT,
RETRY_RECEIPT_MAX_COUNT_RESET_AGE,
PREKEY_FORCE_REFRESH_INTERVAL
);
@VisibleForTesting
@ -259,7 +265,10 @@ public final class FeatureFlags {
CALLING_REACTIONS,
NOTIFICATION_THUMBNAIL_BLOCKLIST,
CALLING_RAISE_HAND,
VIDEO_RECORD_1X_ZOOM
VIDEO_RECORD_1X_ZOOM,
RETRY_RECEIPT_MAX_COUNT,
RETRY_RECEIPT_MAX_COUNT_RESET_AGE,
PREKEY_FORCE_REFRESH_INTERVAL
);
/**
@ -439,6 +448,20 @@ public final class FeatureFlags {
return getLong(RETRY_RESPOND_MAX_AGE, TimeUnit.DAYS.toMillis(14));
}
/**
* The max number of retry receipts sends we allow (within @link{#retryReceiptMaxCountResetAge()}) before we consider the volume too large and stop responding.
*/
public static long retryReceiptMaxCount() {
return getLong(RETRY_RECEIPT_MAX_COUNT, 10);
}
/**
* If the last retry receipt send was older than this, then we reset the retry receipt sent count. (For use with @link{#retryReceiptMaxCount()})
*/
public static long retryReceiptMaxCountResetAge() {
return getLong(RETRY_RECEIPT_MAX_COUNT_RESET_AGE, TimeUnit.HOURS.toMillis(3));
}
/** How long a sender key can live before it needs to be rotated. */
public static long senderKeyMaxAge() {
return Math.min(getLong(SENDER_KEY_MAX_AGE, TimeUnit.DAYS.toMillis(14)), TimeUnit.DAYS.toMillis(90));
@ -595,6 +618,11 @@ public final class FeatureFlags {
return getBoolean(VIDEO_RECORD_1X_ZOOM, false);
}
/** How often we allow a forced prekey refresh. */
public static long preKeyForceRefreshInterval() {
return getLong(PREKEY_FORCE_REFRESH_INTERVAL, TimeUnit.HOURS.toMillis(1));
}
/** Only for rendering debug info. */
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
return new TreeMap<>(REMOTE_VALUES);

View file

@ -0,0 +1,15 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.util
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.JobManager
/** Starts a new chain with this job. */
fun Job.asChain(): JobManager.Chain {
return ApplicationDependencies.getJobManager().startChain(this)
}

View file

@ -36,4 +36,8 @@ message AttachmentUploadJobData {
uint64 attachmentId = 1;
reserved /*attachmentUniqueId*/ 2;
optional ResumableUpload uploadSpec = 3;
}
message PreKeysSyncJobData {
bool forceRefreshRequested = 1;
}

View file

@ -38,6 +38,7 @@
android:layout_marginHorizontal="24dp"
android:layout_marginTop="36dp"
android:hint="@string/CreditCardFragment__card_number"
android:theme="@style/Signal.ThemeOverlay.TextInputLayout"
app:errorEnabled="true"
app:layout_constraintTop_toBottomOf="@id/description">
@ -65,6 +66,7 @@
android:layout_marginTop="18dp"
android:hint="@string/CreditCardFragment__mm_yy"
android:paddingEnd="18dp"
android:theme="@style/Signal.ThemeOverlay.TextInputLayout"
app:errorEnabled="true"
app:layout_constraintEnd_toStartOf="@id/card_cvv_wrapper"
app:layout_constraintStart_toStartOf="parent"
@ -92,6 +94,7 @@
android:layout_marginEnd="24dp"
android:hint="@string/CreditCardFragment__cvv"
android:paddingStart="18dp"
android:theme="@style/Signal.ThemeOverlay.TextInputLayout"
app:errorEnabled="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/card_expiry_wrapper"

View file

@ -401,6 +401,7 @@
android:layout_marginStart="16dp"
android:layout_marginTop="30dp"
android:src="@drawable/symbol_share_android_24"
app:tint="@color/signal_colorOnPrimaryContainer"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

View file

@ -39,11 +39,11 @@
android:id="@+id/username_box_fill"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/username_edit_box_fill"
android:layout_marginRight="@dimen/dsl_settings_gutter"
android:background="@drawable/username_edit_box_fill"
app:layout_constraintBottom_toBottomOf="@id/username_text_wrapper"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="@id/username_text_wrapper"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/username_text_wrapper" />
<com.google.android.material.textfield.TextInputLayout
@ -54,14 +54,15 @@
android:layout_marginLeft="@dimen/dsl_settings_gutter"
android:layout_marginTop="24dp"
android:layout_marginRight="16dp"
android:theme="@style/Signal.ThemeOverlay.TextInputLayout"
app:boxStrokeColor="@color/signal_colorPrimary"
app:boxStrokeWidth="0dp"
app:boxStrokeWidthFocused="0dp"
app:errorTextAppearance="@style/Signal.Text.Zero"
app:expandedHintEnabled="false"
app:layout_constrainedWidth="true"
app:layout_constraintRight_toLeftOf="@id/suffix_progress"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/suffix_progress"
app:layout_constraintTop_toBottomOf="@id/summary"
app:suffixTextColor="@color/signal_colorOnSurface">
@ -74,9 +75,9 @@
android:imeOptions="actionNext"
android:importantForAutofill="no"
android:inputType="text"
android:layoutDirection="ltr"
android:maxLines="1"
android:minHeight="56dp"
android:layoutDirection="ltr">
android:minHeight="56dp">
<requestFocus />
</EditText>
@ -130,8 +131,8 @@
android:layout_height="2dp"
android:background="@color/signal_colorPrimary"
app:layout_constraintBottom_toBottomOf="@id/username_text_wrapper"
app:layout_constraintRight_toRightOf="@id/discriminator_text"
app:layout_constraintLeft_toLeftOf="@id/username_text_wrapper" />
app:layout_constraintLeft_toLeftOf="@id/username_text_wrapper"
app:layout_constraintRight_toRightOf="@id/discriminator_text" />
<TextView
android:id="@+id/username_error"
@ -142,8 +143,8 @@
android:layout_marginTop="4dp"
android:textColor="@color/signal_colorError"
android:visibility="gone"
app:layout_constraintRight_toRightOf="@id/username_description"
app:layout_constraintLeft_toLeftOf="@id/username_description"
app:layout_constraintRight_toRightOf="@id/username_description"
app:layout_constraintTop_toBottomOf="@id/username_text_wrapper"
tools:text="Error something bad happened. Very super long error message that wraps"
tools:visibility="visible" />
@ -159,8 +160,8 @@
android:text="@string/UsernameEditFragment__choose_your_username"
android:textAppearance="@style/Signal.Text.BodyLarge"
android:textColor="@color/signal_colorOnSurfaceVariant"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/icon" />
<org.thoughtcrime.securesms.util.views.LearnMoreTextView

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Skrap</string>
<string name="UsernameEditFragment_successfully_removed_username">Gebruikersnaam suksesvol verwyder.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Netwerkfout teëgekom.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Hierdie gebruikersnaam is reeds in gebruik.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Gebruikersname kan slegs aZ, 09 en onderstrepings bevat.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Gebruikersname kan nie met \'n syfer begin nie.</string>
@ -3152,7 +3154,7 @@
<string name="preferences__dark_theme">Donker</string>
<string name="preferences__appearance">Voorkoms</string>
<string name="preferences__theme">Tema</string>
<string name="preferences__chat_color_and_wallpaper">Kletskleur &amp; amp; muurpapier</string>
<string name="preferences__chat_color_and_wallpaper">Kletskleur &amp; muurpapier</string>
<!-- Clickable settings text allowing the user to change the icon visible on their phone\'s home screen. -->
<string name="preferences__app_icon">Toepassingikoon</string>
<!-- Approval for changing the app icon. -->

View file

@ -2615,6 +2615,8 @@
<string name="UsernameEditFragment_delete">حذف</string>
<string name="UsernameEditFragment_successfully_removed_username">تم حذف اسم المُستخدم بنجاح.</string>
<string name="UsernameEditFragment_encountered_a_network_error">حدث خطأ فى الشبكة</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">اسم المُستخدم هذا غير متوفّر.</string>
<string name="UsernameEditFragment_usernames_can_only_include">أسماء المُستخدمين يجب أن تحتوي فقط على الحروف الأبجدية، 0-9، أو \"_\".</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">اسم المُستخدم لا يمكن أن يبدأ برقم.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Sil</string>
<string name="UsernameEditFragment_successfully_removed_username">İstifadəçi adı uğurla çıxarıldı.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Şəbəkə xətası ilə qarşılaşıldı.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Bu istifadəçi adı götürülüb.</string>
<string name="UsernameEditFragment_usernames_can_only_include">İstifadəçi adları yalnız aZ, 09 və altdan xətt ehtiva edə bilər.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">İstifadəçi adı nömrə ilə başlaya bilməz.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Изтриване</string>
<string name="UsernameEditFragment_successfully_removed_username">Успешно премахнато потребителско име.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Възникна мрежова грешка.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Това потребителско име е взето.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Потребителските имена могат да включват само aZ, 09 и долни черти.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Потребителските имена не могат да започват с цифра.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">মুছে ফেলুন</string>
<string name="UsernameEditFragment_successfully_removed_username">সফলভাবে ব্যবহারকারীর নাম মুছে ফেলা হয়েছে।</string>
<string name="UsernameEditFragment_encountered_a_network_error">একটি নেটওয়ার্ক ত্রুটি সম্মুখীন।</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">এই ব্যবহারকারীর নাম ব্যবহৃত হয়েছে।</string>
<string name="UsernameEditFragment_usernames_can_only_include">ব্যবহারকারীর নামে থাকতে পারবে শুধুমাত্র aZ, 09, ও আন্ডারস্কোর।</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">ব্যবহারকারীর নাম সংখ্যা দিয়ে শুরু করতে পারবে না।</string>

View file

@ -2455,6 +2455,8 @@
<string name="UsernameEditFragment_delete">Izbriši</string>
<string name="UsernameEditFragment_successfully_removed_username">Uspješno je uklonjeno korisničko ime.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Došlo je do greške u mreži.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Ovo korisničko ime već je zauzeto.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Korisničko ime može sadržavati samo slova bez kvačica, brojeve i donju crtu.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Korisničko ime ne može početi brojem.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Suprimeix</string>
<string name="UsernameEditFragment_successfully_removed_username">L\'àlies s\'ha eliminat correctament.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Hi ha hagut un error de xarxa.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Aquest àlies ja està agafat.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Els àlies només poden incloure a-z, 0-9 i _.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Els àlies no poden començar amb un número.</string>

View file

@ -2455,6 +2455,8 @@
<string name="UsernameEditFragment_delete">Odstranit</string>
<string name="UsernameEditFragment_successfully_removed_username">Uživatelské jméno bylo úspěšně odstraněno.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Došlo k chybě v síti.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Toto uživatelské jméno je obsazené.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Uživatelská jména mohou obsahovat pouze aZ, 09 a podtržítka.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Uživatelská jména nesmí začínat číslicí.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Slet</string>
<string name="UsernameEditFragment_successfully_removed_username">Brugernavn slettet.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Registreret en netværksfejl</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Brugernavnet er optaget.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Brugernavne må kun indeholde a-Z, 0-9 og underscores.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Brugernavne kan ikke starte med et tal.</string>

View file

@ -1967,7 +1967,7 @@
<!-- Displayed in a sheet row describing that the user has marked this contact as \'verified\' from within the app -->
<string name="AboutSheet__verified">Verifiziert</string>
<!-- Displayed in bottom sheet describing that the user has no direct messages with this person. The placeholder is a person\'s name. -->
<string name="AboutSheet__no_direct_message">Keine Direktnachrichten mit %1$s</string>
<string name="AboutSheet__no_direct_message">Keine Einzelchats mit %1$s</string>
<!-- Explains that the given user (placeholder is short name) is in the users system contact -->
<string name="AboutSheet__s_is_in_your_system_contacts">%1$s ist in deinen Telefonkontakten</string>
<!-- Notice in a row when user has no groups in common -->
@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Löschen</string>
<string name="UsernameEditFragment_successfully_removed_username">Nutzername erfolgreich entfernt.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Ein Netzfehler ist aufgetreten.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Dieser Nutzername ist vergeben.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Nutzernamen dürfen nur aZ, 09 und Unterstriche beinhalten.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Nutzernamen dürfen nicht mit einer Ziffer beginnen.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Διαγραφή</string>
<string name="UsernameEditFragment_successfully_removed_username">Το όνομα χρήστη αφαιρέθηκε με επιτυχία.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Υπήρξε σφάλμα δικτύου.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Αυτό το όνομα χρήστη χρησιμοποιείται ήδη.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Τα ονόματα χρήστη μπορούν να περιέχουν μόνο λατινικούς χαρακτήρες a-Z, αριθμούς 0-9 και κάτω παύλες.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Τα ονόματα χρήστη δεν μπορούν να αρχίζουν με αριθμό</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Eliminar</string>
<string name="UsernameEditFragment_successfully_removed_username">Alias eliminado con éxito.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Fallo en la conexión de red.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Alias en uso por otra persona.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Tu alias solo puede contener aZ, 09 y _.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">El alias no puede comenzar por un número.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Kustuta</string>
<string name="UsernameEditFragment_successfully_removed_username">Kasutajanimi edukalt eemaldatud.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Esines võrgutõrge.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">See kasutajanimi on võetud.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Kasutajanimed võivad sisaldada ainult aZ, 09 ja alakriipse.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Kasutajanimed ei tohi alata numbriga.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Ezabatu</string>
<string name="UsernameEditFragment_successfully_removed_username">Kendu da erabiltzaile-izena.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Sare errore bat izan da.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Erabiltzaile-izen hau hartuta dago.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Erabiltzaile-izenetan bakarrik ondokoak daude onartuta: a-Z, 0-9 eta azpimarrak.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Erabiltzaile-izenak ezin dira zenbaki batez hasi.</string>

View file

@ -19,7 +19,7 @@
<!-- Removed by excludeNonTranslatables <string name="donate_faq_url" translatable="false">https://support.signal.org/hc/articles/360031949872#donate</string> -->
<string name="yes">بله</string>
<string name="no">خیر</string>
<string name="no">نه</string>
<string name="delete">پاک کردن</string>
<string name="please_wait">لطفاً صبر کنید…</string>
<string name="save">ذخیره</string>
@ -186,11 +186,11 @@
<!-- Dialog button to report as spam only -->
<string name="BlockUnblockDialog_report_spam">به‌عنوان هرزنامه گزارش شود</string>
<!-- Dialog message when reporting spam of an individual (1:1 conversation) -->
<string name="BlockUnblockDialog_report_spam_description">سیگنال مطلع خواهد شد که این فرد ممکن است در حال ارسال هرزنامه باشد. سیگنال نمی‌تواند محتوای گفتگوها را ببیند.</string>
<string name="BlockUnblockDialog_report_spam_description">سیگنال مطلع خواهد شد که این فرد ممکن است در حال ارسال هرزنامه باشد. سیگنال نمی‌تواند محتوای هیچ گفتگویی را ببیند.</string>
<!-- Dialog message when reporting spam of a group and we can determine the group member that invited you, placeholder is a name -->
<string name="BlockUnblockDialog_report_spam_group_named_adder">سیگنال مطلع خواهد شد که %1$s که شما را به این گروه دعوت کرده ممکن است در حال ارسال هرزنامه باشد. سیگنال نمی‌تواند محتوای گفتگوها را ببیند.</string>
<string name="BlockUnblockDialog_report_spam_group_named_adder">سیگنال مطلع خواهد شد که %1$s که شما را به این گروه دعوت کرده ممکن است در حال ارسال هرزنامه باشد. سیگنال نمی‌تواند محتوای هیچ گفتگویی را ببیند.</string>
<!-- Dialog message when reporting spam of a group and we cannot determine the group member that invited you -->
<string name="BlockUnblockDialog_report_spam_group_unknown_adder">سیگنال مطلع خواهد شد که فردی که شما را به این گروه دعوت کرده ممکن است در حال ارسال هرزنامه باشد. سیگنال نمی‌تواند محتوای گفتگوها را ببیند.</string>
<string name="BlockUnblockDialog_report_spam_group_unknown_adder">سیگنال مطلع خواهد شد که فردی که شما را به این گروه دعوت کرده ممکن است در حال ارسال هرزنامه باشد. سیگنال نمی‌تواند محتوای هیچ گفتگویی را ببیند.</string>
<!-- BucketedThreadMedia -->
<string name="BucketedThreadMedia_Today">امروز</string>
@ -530,7 +530,7 @@
<!-- Menu otpion to accept a message request in a conversation -->
<string name="ConversationFragment_accept">پذیرفتن</string>
<!-- Menu option to delete an entire chat in a conversation -->
<string name="ConversationFragment_delete_chat">حذف گفتگو</string>
<string name="ConversationFragment_delete_chat">پاک کردن گفتگو</string>
<!-- Menu option to unblock in a conversation -->
<string name="ConversationFragment_unblock">رفع مسدودیت</string>
<!-- Dialog title shown after reporting spam and tapping the conversation item -->
@ -551,21 +551,21 @@
<!-- Dialog subtitle when showing tips for a group conversation -->
<string name="SafetyTips_subtitle_group">این درخواست را بادقت مرور کنید. هیچ‌یک از مخاطبان شما یا کسانی که با آن‌ها گفتگو می‌کنید در این گروه نیستند. چند چیز که باید در مورد آن‌ها احتیاط کنید:</string>
<!-- Button text to move to the previous tip-->
<string name="SafetyTips_previous_tip">نکته قبلی</string>
<string name="SafetyTips_previous_tip">نکتۀ قبلی</string>
<!-- Button text to move to the next tip -->
<string name="SafetyTips_next_tip">نکته بعدی</string>
<string name="SafetyTips_next_tip">نکتۀ بعدی</string>
<!-- Title of tip 1 -->
<string name="SafetyTips_tip1_title">هرزنامه‌های پولی یا رمزارز</string>
<string name="SafetyTips_tip1_title">کلاه‌برداری‌های پولی یا رمزارز</string>
<!-- Message of tip 1 -->
<string name="SafetyTips_tip1_message">If someone you dont know messages about cryptocurrency (like Bitcoin) or a financial opportunity, be careful—its likely spam.</string>
<!-- Title of tip 2 -->
<string name="SafetyTips_tip2_title">پیام‌های مبهم یا نامرتبط</string>
<!-- Message of tip 2 -->
<string name="SafetyTips_tip2_message">ارسال‌کنندگان هرزنامه معمولاً با پیام ساده‌ای مثل «سلام» شروع می‌کنند تا شما را درگیر کنند. اگر پاسخ دهید ممکن است درگیر ادامه ماجرا شوید.</string>
<string name="SafetyTips_tip2_message">ارسال‌کنندگان هرزنامه معمولاً با پیام ساده‌ای مثل «سلام» شروع می‌کنند تا شما را درگیر کنند. اگر پاسخ دهید ممکن است گفتگو را بیشتر پیش ببرند تا شما را درگیر کنند.</string>
<!-- Title of tip 3 -->
<string name="SafetyTips_tip3_title">پیام‌های دارای پیوند</string>
<!-- Message of tip 3 -->
<string name="SafetyTips_tip3_message">در مورد پیام‌هایی که از افراد ناشناس دریافت می‌کنید و داخل آن‌ها پیوند به وب‌سایتها وجود دارد احتیاط کنید. هیچ‌وقت پیوندی که از افراد غیرقابل‌اعتماد دریافت کرده‌اید را باز نکنید.</string>
<string name="SafetyTips_tip3_message">در مورد پیام‌هایی که از افراد ناشناس دریافت می‌کنید و داخل آن‌ها پیوند به وب‌سایتی وجود دارد احتیاط کنید. هیچ‌وقت پیوندی که از افراد غیرقابل‌اعتماد دریافت کرده‌اید را باز نکنید.</string>
<!-- Title of tip 4 -->
<string name="SafetyTips_tip4_title">کسب‌وکارها و سازمان‌های جعلی</string>
<!-- Message of tip 4 -->
@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">پاک کردن</string>
<string name="UsernameEditFragment_successfully_removed_username">نام کاربری با موفقیت حذف شد.</string>
<string name="UsernameEditFragment_encountered_a_network_error">خطای شبکه رخ داد.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">این نام کاربری گرفته شده است.</string>
<string name="UsernameEditFragment_usernames_can_only_include">نام‌های کاربری فقط می‌توانند حاوی حروف A تا Z، اعداد ۰ تا ۹ و _ باشند.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">نام‌های کاربری نمی‌توانند با عدد آغاز شوند.</string>
@ -6565,7 +6567,7 @@
<!-- Entry placeholder for find by phone number -->
<string name="FindByActivity__phone_number">شماره تلفن</string>
<!-- Help text under user entry for find by username -->
<string name="FindByActivity__enter_username_description">نام کاربری و سپس نقطه و مجموعه اعداد آن را وارد کنید.</string>
<string name="FindByActivity__enter_username_description">یک نام کاربری و سپس یک نقطه و مجموعۀ اعداد آن را وارد کنید.</string>
<!-- Content description for next action button -->
<string name="FindByActivity__next">بعدی</string>
<!-- Placeholder text for search input for selecting country code -->

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Poista</string>
<string name="UsernameEditFragment_successfully_removed_username">Käyttäjänimen poisto onnistui.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Verkkovirhe.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Tämä käyttäjänimi on varattu.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Käyttäjänimet voivat sisältää vain merkkejä az, 09 ja alaviivoja.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Käyttäjänimi ei voi alkaa numerolla.</string>

View file

@ -1601,7 +1601,7 @@
<!-- Update message shown in chat after reporting it as spam -->
<string name="MessageRecord_reported_as_spam">Signalé comme spam</string>
<!-- Update message shown in chat after accept a message request -->
<string name="MessageRecord_you_accepted_the_message_request">Vous avez accepté la demande de message</string>
<string name="MessageRecord_you_accepted_the_message_request">Vous avez accepté linvitation par message</string>
<!-- MessageRequestBottomView -->
<string name="MessageRequestBottomView_accept">Accepter</string>
@ -2207,7 +2207,7 @@
<string name="SupportEmailUtil_device_info">Info sur lappareil :</string>
<string name="SupportEmailUtil_android_version">Version dAndroid :</string>
<string name="SupportEmailUtil_signal_version">Version de Molly :</string>
<string name="SupportEmailUtil_signal_package">Paquet Molly :</string>
<string name="SupportEmailUtil_signal_package">Package Molly :</string>
<string name="SupportEmailUtil_registration_lock">Blocage de linscription :</string>
<string name="SupportEmailUtil_locale">Paramètres régionaux :</string>
@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Supprimer</string>
<string name="UsernameEditFragment_successfully_removed_username">Le nom dutilisateur a été effacé avec succès.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Une erreur réseau est survenue.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Ce nom dutilisateur existe déjà.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Les noms dutilisateur ne peuvent inclure que des caractères de A à Z, 0 à 9 et des tirets bas.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Un nom dutilisateur ne peut pas commencer par un chiffre.</string>
@ -2588,7 +2590,7 @@
<!-- WebRtcAudioOutputToggle -->
<!-- Label for a dialog asking the user to switch the audio output device during a call -->
<string name="WebRtcAudioOutputToggle__audio_output">Sortie son</string>
<string name="WebRtcAudioOutputToggle__audio_output">Sortie audio</string>
<!-- Audio output option referring to the earpiece built into the phone -->
<string name="WebRtcAudioOutputToggle__phone_earpiece">Écouteur de téléphone</string>
<!-- Audio output option referring to the louder speaker built into the phone -->
@ -2677,8 +2679,8 @@
<string name="conversation_activity__emoji_toggle_description">Afficher/masquer le clavier des émojis</string>
<string name="conversation_activity__attachment_thumbnail">Imagette de fichiers joints</string>
<string name="conversation_activity__quick_attachment_drawer_toggle_camera_description">Afficher/masquer le tiroir de lappareil photo à basse résolution</string>
<string name="conversation_activity__quick_attachment_drawer_record_and_send_audio_description">Enregistrer et envoyer une fichier son joint</string>
<string name="conversation_activity__quick_attachment_drawer_lock_record_description">Verrouiller lenregistrement de fichiers son joints</string>
<string name="conversation_activity__quick_attachment_drawer_record_and_send_audio_description">Enregistrer et envoyer un message vocal en pièce jointe</string>
<string name="conversation_activity__quick_attachment_drawer_lock_record_description">Verrouiller lenregistrement de pièces jointes au format audio</string>
<string name="conversation_activity__message_could_not_be_sent">Le message na pas pu être envoyé. Veuillez vérifier votre connexion et réessayer.</string>
<!-- conversation_input_panel -->

View file

@ -2535,6 +2535,8 @@
<string name="UsernameEditFragment_delete">Scrios</string>
<string name="UsernameEditFragment_successfully_removed_username">D\'éirigh le hainm úsáideora a bhaint.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Tharla earráid líonra</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Tá an t-ainm úsáideora sin gafa cheana.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Ní cheadaítear ach a-Z, 0-9, agus fostríoca in ainmneacha úsáideora.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Ní cheadaítear uimhir ag tús ainm úsáideora.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Eliminar</string>
<string name="UsernameEditFragment_successfully_removed_username">Nome de usuario eliminado satisfactoriamente.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Houbo un fallo na rede.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Este nome de usuario xa está en uso.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Os nomes de usuario só poden incluír a-Z, 0-9 e guións baixos.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Os nomes de usuario non poden comezar por un número.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">ડિલીટ કરો</string>
<string name="UsernameEditFragment_successfully_removed_username">યુઝરનેમ સફળતાપૂર્વક દૂર કર્યું.</string>
<string name="UsernameEditFragment_encountered_a_network_error">નેટવર્ક ભૂલ મળી.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">આ યુઝરનેમ લેવાઈ ગયું છે.</string>
<string name="UsernameEditFragment_usernames_can_only_include">યુઝરનેમમાં માત્ર aZ, 09 અને અન્ડરસ્કોર શામેલ હોઈ શકે છે.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">યુઝરનેમ સંખ્યા સાથે શરૂ થઈ શકતા નથી.</string>
@ -3152,7 +3154,7 @@
<string name="preferences__dark_theme">ડાર્ક</string>
<string name="preferences__appearance">દેખાવ</string>
<string name="preferences__theme">થીમ</string>
<string name="preferences__chat_color_and_wallpaper">ચેટ કલર &amp; amp; વૉલપેપર</string>
<string name="preferences__chat_color_and_wallpaper">ચેટ કલર &amp; વૉલપેપર</string>
<!-- Clickable settings text allowing the user to change the icon visible on their phone\'s home screen. -->
<string name="preferences__app_icon">ઍપ આઇકન</string>
<!-- Approval for changing the app icon. -->

View file

@ -536,7 +536,7 @@
<!-- Dialog title shown after reporting spam and tapping the conversation item -->
<string name="ConversationFragment_reported_spam">रिपोर्ट किया गया स्पैम</string>
<!-- Dialog message shown after reporting spam and tapping the conversation item -->
<string name="ConversationFragment_reported_spam_message">Signal has been notified that this person may be sending spam. Signal cant see the content of any chats.</string>
<string name="ConversationFragment_reported_spam_message">Signal को सूचित किया गया है कि यह व्यक्ति स्पैम भेज सकता है। Signal किसी भी चैट की सामग्री नहीं देख सकता।</string>
<!-- Toast shown after reporting spam and tapping the conversation item -->
<string name="ConversationFragment_reported_as_spam">स्पैम के रूप में रिपोर्ट किया गया</string>
<!-- Toast shown after reporting and blocking a conversation -->
@ -557,7 +557,7 @@
<!-- Title of tip 1 -->
<string name="SafetyTips_tip1_title">क्रिप्टो या मनी स्कैम</string>
<!-- Message of tip 1 -->
<string name="SafetyTips_tip1_message">If someone you dont know messages about cryptocurrency (like Bitcoin) or a financial opportunity, be careful—its likely spam.</string>
<string name="SafetyTips_tip1_message">यदि कोई व्यक्ति जिसे आप नहीं जानते हैं, क्रिप्टोकरेंसी (जैसे बिटकॉइन) या वित्तीय अवसर के बारे में संदेश भेजता है, तो सावधान रहें - यह संभावित रूप से स्पैम है।</string>
<!-- Title of tip 2 -->
<string name="SafetyTips_tip2_title">अस्पष्ट या अप्रासंगिक संदेश</string>
<!-- Message of tip 2 -->
@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">डिलीट करें</string>
<string name="UsernameEditFragment_successfully_removed_username">सफलतापूर्वक यूज़रनेम हटा दिया गया।</string>
<string name="UsernameEditFragment_encountered_a_network_error">एक नेटवर्क त्रुटि का सामना करना पड़ा|</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">यह यूज़रनेम ले लिया गया है।</string>
<string name="UsernameEditFragment_usernames_can_only_include">यूज़रनेम में केवल aZ, 09, और अंडरस्कोर शामिल हो सकते हैं।</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">यूज़रनेम एक संख्या से शुरू नहीं हो सकते हैं।</string>
@ -2318,7 +2320,7 @@
<!-- Displayed when the chosen discriminator is 00 -->
<string name="UsernameEditFragment__this_number_cant_be_00">यह संख्या 00 नहीं हो सकती। 1-9 के बीच का कोई अंक दर्ज करें</string>
<!-- Displayed when the chosen discriminator starts with 0 and has a length > 2 -->
<string name="UsernameEditFragment__this_number_cant_start_with_0">Numbers with more than 2 digits can\'t start with 0</string>
<string name="UsernameEditFragment__this_number_cant_start_with_0">2 से अधिक अंकों वाली संखाएं 0 से शुरू नहीं हो सकतीं</string>
<!-- The body of an alert dialog asking the user to confirm that they want to recover their username -->
<string name="UsernameEditFragment_recovery_dialog_confirmation">आपके यूज़रनेम को रिकवर करने से आपका मौजूदा QR कोड और लिंक फिर से स्थापित हो जाएगा। क्या आपको यकीन है?</string>
<!-- The body of an alert dialog asking the user to confirm that they want to change their username, even if it resets their link -->

View file

@ -2455,6 +2455,8 @@
<string name="UsernameEditFragment_delete">Izbriši</string>
<string name="UsernameEditFragment_successfully_removed_username">Korisničko ime uspješno je uklonjeno.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Došlo je do mrežne pogreške.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Korisničko ime je zauzeto.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Korisnička imena smiju sadržavati samo a-Z, 0-9 i podvlake.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Korisnička imena ne mogu započinjati s brojem.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Törlés</string>
<string name="UsernameEditFragment_successfully_removed_username">A felhasználónév eltávolítása sikeres volt.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Hálózati hiba történt.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">A felhasználónév foglalt.</string>
<string name="UsernameEditFragment_usernames_can_only_include">A felhasználónevek csak a-Z, 0-9 és alsóvonás karaktereket tartalmazhatnak.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">A felhasználónevek nem kezdődhetnek számmal.</string>

View file

@ -2215,6 +2215,8 @@
<string name="UsernameEditFragment_delete">Hapus</string>
<string name="UsernameEditFragment_successfully_removed_username">Nama pengguna berhasil dihapus.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Terjadi galat jaringan.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Nama pengguna telah digunakan.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Nama pengguna hanya boleh berisi aZ, 09, dan garisbawah.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Nama pengguna tidak boleh dimulai dengan angka.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Elimina</string>
<string name="UsernameEditFragment_successfully_removed_username">Nome utente rimosso con successo.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Si è verificato un errore di rete.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Questo nome utente è già in uso.</string>
<string name="UsernameEditFragment_usernames_can_only_include">I nomi utente possono includere solo aZ, 09 e trattini bassi.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">I nomi utente non possono iniziare con un numero.</string>

View file

@ -2455,6 +2455,8 @@
<string name="UsernameEditFragment_delete">מחיקה</string>
<string name="UsernameEditFragment_successfully_removed_username">שם משתמש הוסר בהצלחה.</string>
<string name="UsernameEditFragment_encountered_a_network_error">היישום נתקל בשגיאת רשת.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">שם משתמש זה תפוס.</string>
<string name="UsernameEditFragment_usernames_can_only_include">שמות משתמש יכולים להכיל רק aZ, 09, וקווים תחתונים.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">שמות משתמש אינם יכולים להתחיל במספר.</string>

View file

@ -2215,6 +2215,8 @@
<string name="UsernameEditFragment_delete">消去する</string>
<string name="UsernameEditFragment_successfully_removed_username">ユーザーネームを削除しました。</string>
<string name="UsernameEditFragment_encountered_a_network_error">ネットワークエラーが発生しました。</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">このユーザーネームは既に使用されています。</string>
<string name="UsernameEditFragment_usernames_can_only_include">ユーザーネームには半角の英数字とアンダーバーのみ使用できます。</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">ユーザーネームの先頭に数字は使用できません。</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">წაშლა</string>
<string name="UsernameEditFragment_successfully_removed_username">მომხმარებლის სახელი წარმატებით წაიშალა.</string>
<string name="UsernameEditFragment_encountered_a_network_error">ქსელის ხარვეზს გადავაწყდით.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">ეს მომხმარებლის სახელი დაკავებულია.</string>
<string name="UsernameEditFragment_usernames_can_only_include">მომხმარებლის სახელი შეიძლება შეიცავდეს მხოლოდ aZ, 09 და ქვედა ტირეს.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">მომხმარებლის სახელი ციფრით ვერ დაიწყება.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Жою</string>
<string name="UsernameEditFragment_successfully_removed_username">Пайдаланушы аты өшірілді.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Желі қатесі анықталды.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Бұл пайдаланушы аты бос емес.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Пайдаланушы аттарында aZ, 09 және астыңғы сызықтар бола алады.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Пайдаланушы аттары саннан басталмауы керек.</string>

View file

@ -2215,6 +2215,8 @@
<string name="UsernameEditFragment_delete">លុប</string>
<string name="UsernameEditFragment_successfully_removed_username">លុបឈ្មោះអ្នកប្រើបានជោគជ័យ។</string>
<string name="UsernameEditFragment_encountered_a_network_error">បានជួបប្រទះកំហុសបណ្តាញ។</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">ឈ្មោះអ្នកប្រើនេះត្រូវបានយកហើយ។</string>
<string name="UsernameEditFragment_usernames_can_only_include">ឈ្មោះអ្នកប្រើអាចរួមបញ្ចូលតែ aZ, 09, និងបន្ទាត់ពីក្រោមប៉ុណ្ណោះ។</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">ឈ្មោះអ្នកប្រើមិនអាចចាប់ផ្តើមដោយលេខបានទេ។</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">ಅಳಿಸಿ</string>
<string name="UsernameEditFragment_successfully_removed_username">ಬಳಕೆದಾರ ಹೆಸರನ್ನು ಯಶಸ್ವಿಯಾಗಿ ತೆಗೆದುಹಾಕಲಾಗಿದೆ.</string>
<string name="UsernameEditFragment_encountered_a_network_error">ನೆಟ್‌ವರ್ಕ್ ದೋಷವನ್ನು ಎದುರಿಸಿದೆ.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">ಈ ಯೂಸರ್ ನೇಮ್ ತೆಗೆದುಕೊಳ್ಳಲಾಗಿದೆ.</string>
<string name="UsernameEditFragment_usernames_can_only_include">ಯೂಸರ್ ನೇಮ್ ಗಳು a-Z, 0-9, ಮತ್ತು ಅಡಿಗೆರೆಗಳನ್ನು ಮಾತ್ರ ಒಳಗೊಂಡಿರಬಹುದು.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">ಯೂಸರ್ ನೇಮ್ ಗಳು ಸಂಖ್ಯೆಯಿಂದ ಪ್ರಾರಂಭವಾಗುವುದಿಲ್ಲ.</string>

View file

@ -2215,6 +2215,8 @@
<string name="UsernameEditFragment_delete">삭제</string>
<string name="UsernameEditFragment_successfully_removed_username">사용자 이름이 성공적으로 삭제되었습니다.</string>
<string name="UsernameEditFragment_encountered_a_network_error">네트워크 오류가 발생했습니다.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">사용 중인 사용자 이름입니다.</string>
<string name="UsernameEditFragment_usernames_can_only_include">사용자 이름에는 a~Z, 0~9 및 밑줄만 포함할 수 있습니다.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">사용자 이름은 숫자로 시작할 수 없습니다.</string>

View file

@ -2215,6 +2215,8 @@
<string name="UsernameEditFragment_delete">Өчүрүү</string>
<string name="UsernameEditFragment_successfully_removed_username">Колдонуучу аты өчүрүлдү.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Тармакта ката кетти.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Мындай ат бар.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Колдонуучунун аты a-Z, 0-9 деген тамгалар менен сандардан жана ылдыйкы сызыктан турат.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Колдонуучунун аты сандан башталбайт.</string>

View file

@ -2455,6 +2455,8 @@
<string name="UsernameEditFragment_delete">Ištrinti</string>
<string name="UsernameEditFragment_successfully_removed_username">Naudotojo vardas sėkmingai pašalintas.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Susidurta su tinklo klaida.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Šis naudotojo vardas užimtas.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Naudotojo varduose gali būti tik simboliai aZ, 09 ir pabraukimo brūkšniai.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Naudotojo vardai negali prasidėti skaitmenimi.</string>

View file

@ -2375,6 +2375,8 @@
<string name="UsernameEditFragment_delete">Dzēst</string>
<string name="UsernameEditFragment_successfully_removed_username">Lietotājvārds ir sekmīgi noņemts.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Tīkla kļūda.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Šis lietotājvārds ir aizņemts.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Lietotājvārdi var ietvert tikai aZ, 09 un pasvītrojuma zīmes.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Lietotājvārdi nevar sākties ar skaitli.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Избриши</string>
<string name="UsernameEditFragment_successfully_removed_username">Успешно отстрането корисничко име.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Се случи мрежна грешка.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Корисничкото име е веќе во употреба.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Корисничките имиња можат да се состојат од а-Ш, 0-9 и долни цртички.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Корисничкото име не може да започне со број.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">ഇല്ലാതാക്കൂ</string>
<string name="UsernameEditFragment_successfully_removed_username">ഉപയോക്തൃനാമം നീക്കം ചെയ്തു.</string>
<string name="UsernameEditFragment_encountered_a_network_error">ഒരു നെറ്റ്‌വർക്ക് പിശക് നേരിട്ടു.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">ഈ ഉപയോക്തൃനാമം എടുത്തിട്ടുണ്ട്.</string>
<string name="UsernameEditFragment_usernames_can_only_include">ഉപയോക്തൃനാമങ്ങളിൽ aZ, 09, അണ്ടർ സ്കോറുകൾ എന്നിവ മാത്രമേ ഉൾപ്പെടുത്താനാകൂ.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">ഉപയോക്തൃനാമം ഒരു നമ്പറിൽ ആരംഭിക്കാൻ കഴിയില്ല.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">हटवा</string>
<string name="UsernameEditFragment_successfully_removed_username">वापरकर्तानाव यशस्वीरीत्या काढून टाकले.</string>
<string name="UsernameEditFragment_encountered_a_network_error">एक नेटवर्क त्रुटी आढळली.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">हे वापरकर्तानाव घेतलेले आहे.</string>
<string name="UsernameEditFragment_usernames_can_only_include">वापरकर्तानावांमध्ये फक्त aZ, 09, आणि अंडरस्कोर याचा समावेश असू शकतो.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">वापरकर्तानाव अंकांसोबत सोबत चालू होऊ शकत नाही.</string>

View file

@ -2215,6 +2215,8 @@
<string name="UsernameEditFragment_delete">Padam</string>
<string name="UsernameEditFragment_successfully_removed_username">Berjaya mengalih keluar nama pengguna.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Menghadapi ralat rangkaian.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Nama pengguna ini telah diambil.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Nama pengguna hanya boleh mengandungi aZ, 09 dan garis bawah.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Nama pengguna tidak boleh bermula dengan nombor.</string>

View file

@ -2215,6 +2215,8 @@
<string name="UsernameEditFragment_delete">ဖျက်ရန်</string>
<string name="UsernameEditFragment_successfully_removed_username">အသုံးပြုသူအမည်ကို အောင်မြင်စွာ ဖယ်ရှားခဲ့ပြီး။</string>
<string name="UsernameEditFragment_encountered_a_network_error">ကွန်ယက်အမှားတစ်ခုနှင့် ကြုံတွေ့ခဲ့ရသည်။</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">ဤသုံးစွဲသူအမည်ကို ယူထားပြီးပါပြီ။</string>
<string name="UsernameEditFragment_usernames_can_only_include">သုံးစွဲသူအမည်များတွင် AZ၊ 0-9 နှင့် \"_\" သင်္ကေတများသာ ပါဝင်နိုင်ပါသည်။</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">သုံးစွဲသူအမည်များသည် နံပါတ်နှင့် စတင်၍ မရပါ။</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Slett</string>
<string name="UsernameEditFragment_successfully_removed_username">Brukernavnet er fjernet.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Det oppstod en nettverksfeil.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Dette brukernavnet er tatt.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Brukernavn kan bare inneholde aZ, 09 og understrek.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Brukernavn kan ikke begynne med et tall.</string>

View file

@ -1698,7 +1698,7 @@
<string name="PlacePickerActivity_title">Kaart</string>
<string name="PlacePickerActivity_drop_pin">Aanwijzer</string>
<string name="PlacePickerActivity_accept_address">Adres accepteren</string>
<string name="PlacePickerActivity_accept_address">Adres bevestigen</string>
<!-- PlayServicesProblemFragment -->
<string name="PlayServicesProblemFragment_the_version_of_google_play_services_you_have_installed_is_not_functioning">De versie van Google Play Services die je in gebruik hebt werkt niet zoals het hoort. Installeer Google Play Services opnieuw en probeer het opnieuw.</string>
@ -1967,7 +1967,7 @@
<!-- Displayed in a sheet row describing that the user has marked this contact as \'verified\' from within the app -->
<string name="AboutSheet__verified">Geverifieerd</string>
<!-- Displayed in bottom sheet describing that the user has no direct messages with this person. The placeholder is a person\'s name. -->
<string name="AboutSheet__no_direct_message">Geen directe berichten met %1$s</string>
<string name="AboutSheet__no_direct_message">Geen individuele chat met %1$s</string>
<!-- Explains that the given user (placeholder is short name) is in the users system contact -->
<string name="AboutSheet__s_is_in_your_system_contacts">%1$s staat in je systeemcontactenlijst</string>
<!-- Notice in a row when user has no groups in common -->
@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Verwijderen</string>
<string name="UsernameEditFragment_successfully_removed_username">Gebruikersnaam succesvol verwijderd.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Er is een netwerkfout opgetreden.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Deze gebruikersnaam is al in gebruik.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Gebruikersnamen mogen alleen uit a-z, 0-9 en _ bestaan.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Gebruikersnamen mogen niet met een cijfer beginnen.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">ਮਿਟਾਓ</string>
<string name="UsernameEditFragment_successfully_removed_username">ਵਰਤੋਂਕਾਰ-ਨਾਂ ਸਫਲਤਾਪੂਰਵਕ ਹਟਾਇਆ ਗਿਆ।</string>
<string name="UsernameEditFragment_encountered_a_network_error">ਨੈੱਟਵਰਕ ਗਲਤੀ ਆਈ ਹੈ।</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">ਇਹ ਵਰਤੋਂਕਾਰ ਨਾਂ ਕੋਈ ਹੋਰ ਵਰਤ ਰਿਹਾ ਹੈ।</string>
<string name="UsernameEditFragment_usernames_can_only_include">ਵਰਤੋਂਕਾਰ ਨਾਂ ਵਿੱਚ ਸਿਰਫ਼ aZ, 09 ਅਤੇ ਅੰਡਰਸਕੋਰ ਸ਼ਾਮਲ ਹੋ ਸਕਦੇ ਹਨ।</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">ਵਰਤੋਂਕਾਰ ਨਾਂ ਅੰਕ ਨਾਲ ਸ਼ੁਰੂ ਨਹੀਂ ਹੋ ਸਕਦਾ ਹੈ।</string>

View file

@ -2455,6 +2455,8 @@
<string name="UsernameEditFragment_delete">Usuń</string>
<string name="UsernameEditFragment_successfully_removed_username">Pomyślnie usunięto nazwę użytkownika.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Wystąpił błąd sieci.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Nazwa użytkownika jest zajęta.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Nazwy użytkowników mogą zawierać wyłącznie aZ, 09 i podkreślenia (_).</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Nazwy użytkowników nie mogą zaczynać się od cyfry.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Apagar</string>
<string name="UsernameEditFragment_successfully_removed_username">Usuário excluído com sucesso</string>
<string name="UsernameEditFragment_encountered_a_network_error">Foi encontrado um erro de rede.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Este nome de usuário já está sendo utilizado.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Os nomes de usuário só podem incluir aZ, 09, e sublinhado.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Os nomes de usuários não podem começar com um número.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Eliminar</string>
<string name="UsernameEditFragment_successfully_removed_username">Nome de utilizador removido com sucesso.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Encontrado um erro de rede.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Este nome de utilizador já se encontra em utilização.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Os nomes de utilizadores apenas podem incluir a-Z, 0-9 e underscores.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Os nomes de utilizadores não podem começar com um número.</string>

View file

@ -2375,6 +2375,8 @@
<string name="UsernameEditFragment_delete">Șterge</string>
<string name="UsernameEditFragment_successfully_removed_username">Numele de utilizator a fost eliminat cu succes.</string>
<string name="UsernameEditFragment_encountered_a_network_error">A apărut o eroare de rețea.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Acest nume de utilizator este luat.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Numele de utilizatori pot conține doar a-Z, 0-9, și _.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Numele de utilizator nu pot începe cu un număr.</string>

View file

@ -2455,6 +2455,8 @@
<string name="UsernameEditFragment_delete">Удалить</string>
<string name="UsernameEditFragment_successfully_removed_username">Имя пользователя успешно удалено.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Обнаружена ошибка сети.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Это имя пользователя занято.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Имена пользователей могут содержать только aZ, 09 и нижние подчёркивания.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Имена пользователей не могут начинаться с цифр.</string>

View file

@ -2455,6 +2455,8 @@
<string name="UsernameEditFragment_delete">Vymazať</string>
<string name="UsernameEditFragment_successfully_removed_username">Používateľské meno bolo úspešne odstránené.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Vyskytla sa chyba siete.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Toto používateľské meno je obsadené.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Používateľské mená môžu obsahovať iba znaky a-Z, 0-9 a podčiarkovníky.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Používateľské mená nemôžu začínať číslicou.</string>

View file

@ -2455,6 +2455,8 @@
<string name="UsernameEditFragment_delete">Izbriši</string>
<string name="UsernameEditFragment_successfully_removed_username">Uspešno ste izbrisali uporabniško ime.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Prišlo je do napake na omrežju.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Uporabniško ime je že zasedeno.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Uporabniška imena lahko vsebujejo samo znake a-Z, 0-9 in podčrtaje.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Uporabniška imena se ne smejo začeti s števikami.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Fshije</string>
<string name="UsernameEditFragment_successfully_removed_username">Emri i përdoruesit u hoq me sukses.</string>
<string name="UsernameEditFragment_encountered_a_network_error">U has një gabim rrjeti.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Ky emër përdoruesi është i zënë.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Emrat e përdoruesve mund të përmbajnë vetëm a-Z, 0-9 dhe nënvija.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Emrat e përdoruesve nuk mund të fillojnë me numër.</string>
@ -3150,7 +3152,7 @@
<string name="preferences__linked_devices">Pajisje të lidhura</string>
<string name="preferences__light_theme">E çelët</string>
<string name="preferences__dark_theme">E errët</string>
<string name="preferences__appearance">Dukje</string>
<string name="preferences__appearance">Paraqitja</string>
<string name="preferences__theme">Temë</string>
<string name="preferences__chat_color_and_wallpaper">Ngjyra e sfondit të bisedës &amp; letra e murit</string>
<!-- Clickable settings text allowing the user to change the icon visible on their phone\'s home screen. -->

View file

@ -536,7 +536,7 @@
<!-- Dialog title shown after reporting spam and tapping the conversation item -->
<string name="ConversationFragment_reported_spam">Спам је пријављен</string>
<!-- Dialog message shown after reporting spam and tapping the conversation item -->
<string name="ConversationFragment_reported_spam_message">Signal has been notified that this person may be sending spam. Signal cant see the content of any chats.</string>
<string name="ConversationFragment_reported_spam_message">Signal је обавештен да ова особа можда шаље спам. Signal не може да види садржај ниједног ћаскања.</string>
<!-- Toast shown after reporting spam and tapping the conversation item -->
<string name="ConversationFragment_reported_as_spam">Пријављено је као спам</string>
<!-- Toast shown after reporting and blocking a conversation -->
@ -557,7 +557,7 @@
<!-- Title of tip 1 -->
<string name="SafetyTips_tip1_title">Крипто или новчане преваре</string>
<!-- Message of tip 1 -->
<string name="SafetyTips_tip1_message">If someone you dont know messages about cryptocurrency (like Bitcoin) or a financial opportunity, be careful—its likely spam.</string>
<string name="SafetyTips_tip1_message">Ако вам неко кога не познајете шаље поруке о криптовалути (као што је Bitcoin) или некој финансијској прилици, будите опрезни вероватно је у питању спам.</string>
<!-- Title of tip 2 -->
<string name="SafetyTips_tip2_title">Нејасне или неповезане поруке</string>
<!-- Message of tip 2 -->
@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Обриши</string>
<string name="UsernameEditFragment_successfully_removed_username">Корисничко име уклоњено.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Дошло је до грешке мреже.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Ово корисничко име је заузето.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Корисничко име може да садржи a-Z, 0-9 и _</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Корисничко име не може започети бројем.</string>
@ -2318,7 +2320,7 @@
<!-- Displayed when the chosen discriminator is 00 -->
<string name="UsernameEditFragment__this_number_cant_be_00">Овај број не може бити 00. Унесите цифру од 1 до 9</string>
<!-- Displayed when the chosen discriminator starts with 0 and has a length > 2 -->
<string name="UsernameEditFragment__this_number_cant_start_with_0">Numbers with more than 2 digits can\'t start with 0</string>
<string name="UsernameEditFragment__this_number_cant_start_with_0">Бројеви са више од 2 цифре не могу да почињу са 0</string>
<!-- The body of an alert dialog asking the user to confirm that they want to recover their username -->
<string name="UsernameEditFragment_recovery_dialog_confirmation">Ако вратите корисничко име, ресетоваће вам се постојећи QR код и линк. Да ли сте сигурни?</string>
<!-- The body of an alert dialog asking the user to confirm that they want to change their username, even if it resets their link -->

View file

@ -536,7 +536,7 @@
<!-- Dialog title shown after reporting spam and tapping the conversation item -->
<string name="ConversationFragment_reported_spam">Rapporterade skräppost</string>
<!-- Dialog message shown after reporting spam and tapping the conversation item -->
<string name="ConversationFragment_reported_spam_message">Signal has been notified that this person may be sending spam. Signal cant see the content of any chats.</string>
<string name="ConversationFragment_reported_spam_message">Signal har meddelats att denna person kanske skickar skräppost. Signal kan inte se innehållet i några chattar.</string>
<!-- Toast shown after reporting spam and tapping the conversation item -->
<string name="ConversationFragment_reported_as_spam">Rapporterad som skräppost</string>
<!-- Toast shown after reporting and blocking a conversation -->
@ -557,7 +557,7 @@
<!-- Title of tip 1 -->
<string name="SafetyTips_tip1_title">Krypto- eller penningbedrägerier</string>
<!-- Message of tip 1 -->
<string name="SafetyTips_tip1_message">If someone you dont know messages about cryptocurrency (like Bitcoin) or a financial opportunity, be careful—its likely spam.</string>
<string name="SafetyTips_tip1_message">Om någon du inte känner skickar meddelanden om kryptovalutor (som Bitcoin) eller en finansiell möjlighet ska du vara försiktig det är sannolikt skräppost.</string>
<!-- Title of tip 2 -->
<string name="SafetyTips_tip2_title">Vaga eller irrelevanta meddelanden</string>
<!-- Message of tip 2 -->
@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Ta bort</string>
<string name="UsernameEditFragment_successfully_removed_username">Användarnamn borttaget.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Stött på ett nätverksfel.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Detta användarnamn är taget.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Användarnamn kan bara innehålla aZ, 09 och understreck.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Användarnamn kan inte börja med en siffra.</string>
@ -2318,7 +2320,7 @@
<!-- Displayed when the chosen discriminator is 00 -->
<string name="UsernameEditFragment__this_number_cant_be_00">Det här numret kan inte vara 00. Ange en siffra mellan 19</string>
<!-- Displayed when the chosen discriminator starts with 0 and has a length > 2 -->
<string name="UsernameEditFragment__this_number_cant_start_with_0">Numbers with more than 2 digits can\'t start with 0</string>
<string name="UsernameEditFragment__this_number_cant_start_with_0">Nummer med fler än 2 siffror kan inte börja med 0</string>
<!-- The body of an alert dialog asking the user to confirm that they want to recover their username -->
<string name="UsernameEditFragment_recovery_dialog_confirmation">Om du återställer ditt användarnamn återställs din befintliga QR-kod och länk. Är du säker?</string>
<!-- The body of an alert dialog asking the user to confirm that they want to change their username, even if it resets their link -->

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Futa</string>
<string name="UsernameEditFragment_successfully_removed_username">Jina la mtumiaji limeondolewa kwa mafanikio.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Imekumbana na hitilafu ya mtandao.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Jina hili la mtumiaji limechukuliwa.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Majina ya watumiaji yanaweza kujumuisha tu herufi za A hadi Z, nambari za 0 hadi 9, na vistari-chini.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Majina ya watumiaji hayawezi kuanza kwa nambari.</string>

View file

@ -182,15 +182,15 @@
<!-- Dialog button label to report as spam and block the person -->
<string name="BlockUnblockDialog_report_spam_and_block">புகாரளித்து தடு</string>
<!-- Dialog title for reporting spam -->
<string name="BlockUnblockDialog_report_spam_title">ஸ்பேம் எனப் புகாரளிக்கவா?</string>
<string name="BlockUnblockDialog_report_spam_title">ஸ்பேம் எனப் புகாரளிப்பதா?</string>
<!-- Dialog button to report as spam only -->
<string name="BlockUnblockDialog_report_spam">ஸ்பேம் எனப் புகாரளி</string>
<!-- Dialog message when reporting spam of an individual (1:1 conversation) -->
<string name="BlockUnblockDialog_report_spam_description">இந்த நபர் உங்களுக்கு ஸ்பேம் அனுப்பியிருக்கலாம் என்பதை சிக்னல் உங்களுக்குத் தெரிவிக்கிறது. சாட்களின் உள்ளடக்கம் எதையும் சிக்னலால் பார்க்க முடியவில்லை.</string>
<string name="BlockUnblockDialog_report_spam_description">இந்த நபர் உங்களுக்கு ஸ்பேம் அனுப்புவதாக சிக்னலுக்குத் தெரிவிக்கப்படும். எந்தவொரு சாட்களின் உள்ளடக்கத்தையும் சிக்னலால் பார்க்க முடியாது.</string>
<!-- Dialog message when reporting spam of a group and we can determine the group member that invited you, placeholder is a name -->
<string name="BlockUnblockDialog_report_spam_group_named_adder">உங்களை இந்தக் குழுவிற்கு அழைத்த %1$s, ஸ்பேமை அனுப்பியிருக்கலாம் என்பதை சிக்னல் உங்களுக்குத் தெரிவிக்கிறது. சாட்களின் உள்ளடக்கம் எதையும் சிக்னலால் பார்க்க முடியவில்லை.</string>
<string name="BlockUnblockDialog_report_spam_group_named_adder">உங்களை இந்தக் குழுவிற்கு அழைத்த %1$s, ஸ்பேமை அனுப்பியிருக்கலாம் என்பதை சிக்னலுக்குத் தெரிவிக்கப்படும். எந்தவொரு சாட்களின் உள்ளடக்கத்தையும் சிக்னலால் பார்க்க முடியாது.</string>
<!-- Dialog message when reporting spam of a group and we cannot determine the group member that invited you -->
<string name="BlockUnblockDialog_report_spam_group_unknown_adder">உங்களை இந்தக் குழுவிற்கு அழைத்த நபர், ஸ்பேமை அனுப்பியிருக்கலாம் என்பதை சிக்னல் உங்களுக்குத் தெரிவிக்கிறது. சாட்களின் உள்ளடக்கம் எதையும் சிக்னலால் பார்க்க முடியவில்லை.</string>
<string name="BlockUnblockDialog_report_spam_group_unknown_adder">உங்களை இந்தக் குழுவிற்கு அழைத்த நபர், ஸ்பேமை அனுப்பியிருக்கலாம் என்பதை சிக்னலுக்குத் தெரிவிக்கப்படும். எந்தவொரு சாட்களின் உள்ளடக்கத்தையும் சிக்னலால் பார்க்க முடியாது.</string>
<!-- BucketedThreadMedia -->
<string name="BucketedThreadMedia_Today">இன்று</string>
@ -495,7 +495,7 @@
<string name="ConversationFragment__d_group_members_have_the_same_name">%1$dகுழு உறுப்பினர்களுக்கு ஒரே பெயர் உண்டு.</string>
<string name="ConversationFragment__tap_to_review">மதிப்பாய்வு செய்ய தட்டவும்</string>
<!-- The body of a banner that can show up at the top of a chat, letting the user know that you have two contacts with the same name -->
<string name="ConversationFragment__review_banner_body">இந்த நபரின் பெயரில் உங்களிடம் இன்னொரு தொடர்பு உள்ளது</string>
<string name="ConversationFragment__review_banner_body">இந்த நபருக்கு மற்றொரு தொடர்பின் அதே பெயர் உள்ளது</string>
<string name="ConversationFragment_contact_us">எங்களை தொடர்பு கொள்ள</string>
<string name="ConversationFragment_verify">சரிபார்க்கவும்</string>
<string name="ConversationFragment_not_now">இப்போது இல்லை</string>
@ -526,13 +526,13 @@
<!-- Menu option to report spam in a conversation -->
<string name="ConversationFragment_report_spam">ஸ்பேம் எனப் புகாரளி</string>
<!-- Menu option to block in a conversation -->
<string name="ConversationFragment_block">தடைசெய்</string>
<string name="ConversationFragment_block">தடை செய்</string>
<!-- Menu otpion to accept a message request in a conversation -->
<string name="ConversationFragment_accept">ஒப்புக்கொள்</string>
<!-- Menu option to delete an entire chat in a conversation -->
<string name="ConversationFragment_delete_chat">சாட்டை அழி</string>
<!-- Menu option to unblock in a conversation -->
<string name="ConversationFragment_unblock">தடைநீக்கு</string>
<string name="ConversationFragment_unblock">தடை நீக்கு</string>
<!-- Dialog title shown after reporting spam and tapping the conversation item -->
<string name="ConversationFragment_reported_spam">ஸ்பேம் எனப் புகாரளிக்கப்பட்டது</string>
<!-- Dialog message shown after reporting spam and tapping the conversation item -->
@ -542,12 +542,12 @@
<!-- Toast shown after reporting and blocking a conversation -->
<string name="ConversationFragment_reported_as_spam_and_blocked">ஸ்பேம் எனப் புகாரளிக்கப்பட்டு தடுக்கப்பட்டது</string>
<!-- Dialog message shown after accepting a message request and tapping on options from the conversation event -->
<string name="ConversationFragment_you_accepted_a_message_request_from_s">%1$sஇடமிருந்து பெற்ற மெசேஜ் கோரிக்கையை நீங்கள் ஏற்றுக்கொண்டீர்கள். இது தவறுதலாக நடைபெற்றிருந்தால், கீழே கொடுக்கப்பட்ட நடவடிக்கையிலிருந்து நீங்கள் தேர்ந்தெடுக்கலாம்.</string>
<string name="ConversationFragment_you_accepted_a_message_request_from_s">%1$sஇடமிருந்து பெற்ற மெசேஜ் கோரிக்கையை நீங்கள் ஏற்றுக்கொண்டீர்கள். இது தவறுதலாக நடைபெற்றிருந்தால், கீழே கொடுக்கப்பட்ட நடவடிக்கையிலிருந்து நீங்கள் ஒன்றைத் தேர்ந்தெடுக்கலாம்.</string>
<!-- Title of Safety Tips bottom sheet dialog -->
<string name="SafetyTips_title">பாதுகாப்பு உதவிக்குறிப்புகள்</string>
<!-- Dialog subtitle when showign tips for a 1:1 conversation -->
<string name="SafetyTips_subtitle_individual">உங்களுக்கு பரீட்சயமற்றவரிடமிருந்து பெறப்படும் மெசேஜ் கோரிக்கைகளை ஏற்கும்போது கவனமாக இருங்கள். கவனமாக இருங்கள்:</string>
<string name="SafetyTips_subtitle_individual">உங்களுக்கு தெரியாத நபர்களிடமிருந்து பெறப்படும் மெசேஜ் கோரிக்கைகளை ஏற்கும்போது கவனமாக இருங்கள். கவனமாக இருங்கள்:</string>
<!-- Dialog subtitle when showing tips for a group conversation -->
<string name="SafetyTips_subtitle_group">கோரிக்கையை கவனமாக மதிப்பாய்வு செய்க. உங்கள் தொடர்புகள் அல்லது நீங்கள் சாட் செய்பவர்கள் யாரும் இந்தக் குழுவில் இல்லை. நீங்கள் கவனிக்க வேண்டிய சில விஷயங்கள் இங்கே கொடுக்கப்பட்டுள்ளன:</string>
<!-- Button text to move to the previous tip-->
@ -561,11 +561,11 @@
<!-- Title of tip 2 -->
<string name="SafetyTips_tip2_title">தெளிவற்ற அல்லது பொருத்தமற்ற மெசேஜ்கள்</string>
<!-- Message of tip 2 -->
<string name="SafetyTips_tip2_message">ஸ்பேமர்கள் உங்கள் கவனத்தை ஈர்க்க \"ஹாய்\" போன்ற வழக்கமான மெசேஜ் உடன் தொடங்குவார்கள். அதற்கு நீங்கள் பதிலளித்தால் அவர்கள் உங்களை மேற்கொண்டு ஈடுபடுத்தலாம்.</string>
<string name="SafetyTips_tip2_message">ஸ்பேமர்கள் உங்கள் கவனத்தை ஈர்க்க \"ஹாய்\" போன்ற வழக்கமான மெசேஜ் உடன் தொடங்குவார்கள். அதற்கு நீங்கள் பதிலளித்தால் அவர்கள் உங்களை மேற்கொண்டு உரையாடலைத் தொடரலாம்.</string>
<!-- Title of tip 3 -->
<string name="SafetyTips_tip3_title">இணைப்புகளுடன் கூடிய மெசேஜ்கள்</string>
<!-- Message of tip 3 -->
<string name="SafetyTips_tip3_message">உங்களுக்குப் பரீட்சயமற்ற நபர்களிடமிருந்து இணையதளங்களுக்கான இணைப்புகளுடன் கூடிய மெசேஜ்களைப் பெறும்போது கவனமாக இருங்கள். உங்களுக்கு நம்பிக்கையற்றவரிடமிருந்து இணைப்புகளைப் பெறும்போது ஒருபோதும் பார்வையிடாதீர்கள்.</string>
<string name="SafetyTips_tip3_message">உங்களுக்கு தெரியாத நபர்களிடமிருந்து இணையதளங்களுக்கான இணைப்புகளுடன் கூடிய மெசேஜ்களைப் பெறும்போது கவனமாக இருங்கள். உங்களுக்கு நம்பிக்கையற்றவர்கள் அனுப்பும் இணைப்புகளை ஒருபோதும் பார்வையிடாதீர்கள்.</string>
<!-- Title of tip 4 -->
<string name="SafetyTips_tip4_title">போலி நிறுவனங்கள் மற்றும் அமைப்புகள்</string>
<!-- Message of tip 4 -->
@ -1967,7 +1967,7 @@
<!-- Displayed in a sheet row describing that the user has marked this contact as \'verified\' from within the app -->
<string name="AboutSheet__verified">சரிபார்க்கப்பட்டது</string>
<!-- Displayed in bottom sheet describing that the user has no direct messages with this person. The placeholder is a person\'s name. -->
<string name="AboutSheet__no_direct_message">உங்களுக்கும் %1$sக்கும் இடையில் எந்த மெசேஜ்களும் இல்லை</string>
<string name="AboutSheet__no_direct_message">உங்களுக்கும் %1$sக்கும் இடையில் எந்த நேரடி மெசேஜ்களும் இல்லை</string>
<!-- Explains that the given user (placeholder is short name) is in the users system contact -->
<string name="AboutSheet__s_is_in_your_system_contacts">உங்கள் சிஸ்டம் தொடர்புகளில் %1$s உள்ளார்</string>
<!-- Notice in a row when user has no groups in common -->
@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">நீக்கு</string>
<string name="UsernameEditFragment_successfully_removed_username">பயனர் பெயர் வெற்றிகரமாக அகற்றப்பட்டது.</string>
<string name="UsernameEditFragment_encountered_a_network_error">பிணைய பிழையை எதிர்கொண்டது.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">இந்த பயனர் பெயர் எடுக்கப்பட்டது.</string>
<string name="UsernameEditFragment_usernames_can_only_include">பயனர் பெயர்களில் அ - ஃ, aZ, 09 மற்றும் அடிக்கோடிட்டுக் காட்டலாம்.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">பயனர் பெயர் எண்ணுடன் தொடங்க முடியாது.</string>
@ -4125,9 +4127,9 @@
<!-- ReviewCardDialogFragment -->
<!-- Title of a screen where the user will be prompted to review group members with the same name -->
<string name="ReviewCardDialogFragment__review_members">றுஆய்வு கோரிக்கை</string>
<string name="ReviewCardDialogFragment__review_members">திப்பாய்வு கோரிக்கை</string>
<!-- Title of a screen where the user will be prompted to review a message request matching the name of someone they already know -->
<string name="ReviewCardDialogFragment__review_request">றுஆய்வு கோரிக்கை</string>
<string name="ReviewCardDialogFragment__review_request">திப்பாய்வு கோரிக்கை</string>
<string name="ReviewCardDialogFragment__d_group_members_have_the_same_name">%1$dகுழு உறுப்பினர்களுக்கு ஒரே பெயர் உள்ளது, கீழே உள்ள உறுப்பினர்களை மதிப்பாய்வு செய்து நடவடிக்கை எடுக்க முடிவு செய்யுங்கள்</string>
<string name="ReviewCardDialogFragment__if_youre_not_sure">உங்களுக்கு யார் கோரிக்கையை அனுப்பினார்கள் என்பது உங்களுக்குத் தெரியாவிட்டால், கீழே உள்ள தொடர்புகளை மதிப்பாய்வு செய்து நடவடிக்கை எடுக்கவும்.</string>
<string name="ReviewCardDialogFragment__no_other_groups_in_common">பொதுவான குழுக்கள் இல்லை</string>
@ -6565,7 +6567,7 @@
<!-- Entry placeholder for find by phone number -->
<string name="FindByActivity__phone_number">தொலைபேசி எண்</string>
<!-- Help text under user entry for find by username -->
<string name="FindByActivity__enter_username_description">புள்ளி மற்றும் அதன் எண்களின் தொகுப்பைத் தொடர்ந்து பயனர்பெயரை உள்ளிடவும்.</string>
<string name="FindByActivity__enter_username_description">ஒரு புள்ளி மற்றும் அதன் எண்களின் தொகுப்பைத் தொடர்ந்து பயனர் பெயரை உள்ளிடவும்.</string>
<!-- Content description for next action button -->
<string name="FindByActivity__next">அடுத்து</string>
<!-- Placeholder text for search input for selecting country code -->

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">తొలగించండి</string>
<string name="UsernameEditFragment_successfully_removed_username">యూజర్‌నేమ్‌ను విజయవంతంగా తొలగించాము.</string>
<string name="UsernameEditFragment_encountered_a_network_error">నెట్‌వర్క్ లోపాన్ని ఎదుర్కొంది.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">ఈ వినియోగదారు పేరు తీసుకోబడింది.</string>
<string name="UsernameEditFragment_usernames_can_only_include">వినియోగదారు పేర్లు a-Z, 0-9 మరియు అండర్ స్కోర్‌లను మాత్రమే కలిగి ఉంటాయి.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">వినియోగదారు పేర్లు సంఖ్యతో ప్రారంభం కావు.</string>

View file

@ -2215,6 +2215,8 @@
<string name="UsernameEditFragment_delete">ลบ</string>
<string name="UsernameEditFragment_successfully_removed_username">ลบชื่อผู้ใช้สำเร็จแล้ว</string>
<string name="UsernameEditFragment_encountered_a_network_error">พบความผิดพลาดของเครือข่าย</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">มีผู้ใช้ชื่อนี้แล้ว</string>
<string name="UsernameEditFragment_usernames_can_only_include">ชื่อผู้ใช้จะมีได้เฉพาะ a-Z, 0-9 และ _</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">ชื่อผู้ใช้ไม่สามารถเริ่มต้นด้วยตัวเลข</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Burahin</string>
<string name="UsernameEditFragment_successfully_removed_username">Matagumpay na natanggal ang username.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Nakatagpo ng error sa network.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Nakuha na ang username na ito.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Ang usernames ay pwede lang mag-include ng aZ, 09, at underscores.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Ang mga username ay hindi puwedeng magsimula sa numero.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">Sil</string>
<string name="UsernameEditFragment_successfully_removed_username">Kullanıcı adı başarıyla kaldırıldı.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Bir ağ hatası ile karşılaşıldı.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Bu kullanıcı adı alınmış.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Kullanıcı adları yalnızca a-Z, 0-9 ve alt çizgi içerebilir.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Kullanıcı adları rakam ile başlayamaz.</string>

View file

@ -2215,6 +2215,8 @@
<string name="UsernameEditFragment_delete">ئۆچۈر</string>
<string name="UsernameEditFragment_successfully_removed_username">ئىشلەتكۈچى ئىسمى مۇۋەپپەقىيەتلىك چىقىرىۋېتىلدى.</string>
<string name="UsernameEditFragment_encountered_a_network_error">تور خاتالىقىغا يولۇقتى.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">ئىشلەتكۈچى ئىسمى ئىشلىتىلگەن</string>
<string name="UsernameEditFragment_usernames_can_only_include">ئىشلەتكۈچى ئىسمى پەقەت aZ، 09 ۋە ئاستى سىزىقتىن تۈزۈلىدۇ.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">ئىشلەتكۈچى ئىسمى ساندىن باشلانمايدۇ.</string>

View file

@ -2455,6 +2455,8 @@
<string name="UsernameEditFragment_delete">Видалити</string>
<string name="UsernameEditFragment_successfully_removed_username">Ім\'я користувача видалено.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Виникла помилка мережі.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Це ім\'я користувача вже використовується.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Імена користувачів можуть мати лише такі символи: aZ, 09 та нижнє підкреслювання.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Ім\'я користувача не може починатися з цифри.</string>

View file

@ -2295,6 +2295,8 @@
<string name="UsernameEditFragment_delete">حذف کریں</string>
<string name="UsernameEditFragment_successfully_removed_username">کامیابی کے ساتھ یوزر نیم ہٹا دیا گیا۔</string>
<string name="UsernameEditFragment_encountered_a_network_error">نیٹ ورک کی خرابی کا سامنا کرنا پڑا۔</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">یہ یوزر نیم لے لیا گیا ہے۔</string>
<string name="UsernameEditFragment_usernames_can_only_include">یوزر نیمز میں صرف Z-A ، 09 ، اور انڈر سکور شامل ہوسکتے ہیں۔</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">یوزر نیمز کسی نمبر کے ساتھ شروع نہیں ہوسکتے ہیں۔</string>

View file

@ -2215,6 +2215,8 @@
<string name="UsernameEditFragment_delete">Xóa</string>
<string name="UsernameEditFragment_successfully_removed_username">Xóa tên người dùng thành công.</string>
<string name="UsernameEditFragment_encountered_a_network_error">Lỗi mạng.</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">Tên người dùng này đã được sử dụng.</string>
<string name="UsernameEditFragment_usernames_can_only_include">Tên người dùng chỉ có thể chứa aZ, 09 và dấu gạch dưới.</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">Tên người dùng không thể bắt đầu bằng một chữ số.</string>

View file

@ -2215,6 +2215,8 @@
<string name="UsernameEditFragment_delete">刪除</string>
<string name="UsernameEditFragment_successfully_removed_username">成功移除咗用戶名稱。</string>
<string name="UsernameEditFragment_encountered_a_network_error">網絡有問題。</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">呢個用戶名稱已經畀人用咗喇。</string>
<string name="UsernameEditFragment_usernames_can_only_include">用戶名稱只可以用 aZ、09 同底線符號。</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">用戶名稱冇得用數目字開頭。</string>

View file

@ -2215,6 +2215,8 @@
<string name="UsernameEditFragment_delete">删除</string>
<string name="UsernameEditFragment_successfully_removed_username">成功移除用户名。</string>
<string name="UsernameEditFragment_encountered_a_network_error">网络出错。</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">该用户名已有人使用。</string>
<string name="UsernameEditFragment_usernames_can_only_include">用户名仅能包含字母、数字和下划线。</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">用户名不能以数字开头。</string>

View file

@ -2215,6 +2215,8 @@
<string name="UsernameEditFragment_delete">刪除</string>
<string name="UsernameEditFragment_successfully_removed_username">已成功移除用戶名稱。</string>
<string name="UsernameEditFragment_encountered_a_network_error">遇到網絡問題。</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">此用戶名稱已有人使用。</string>
<string name="UsernameEditFragment_usernames_can_only_include">用戶名稱僅可包含 aZ、09 以及底線。</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">用戶名稱不得以數字開首。</string>

View file

@ -2215,6 +2215,8 @@
<string name="UsernameEditFragment_delete">刪除</string>
<string name="UsernameEditFragment_successfully_removed_username">成功移除使用者名稱。</string>
<string name="UsernameEditFragment_encountered_a_network_error">網路連接錯誤。</string>
<!-- Toast message shown if user exceeds the rate limit for reserving usernames -->
<string name="UsernameEditFragment_rate_limit_exceeded_error">Too many attempts made, please try again later.</string>
<string name="UsernameEditFragment_this_username_is_taken">這個使用者名稱已經被使用。</string>
<string name="UsernameEditFragment_usernames_can_only_include">使用者名稱只能包含aZ09和底線。</string>
<string name="UsernameEditFragment_usernames_cannot_begin_with_a_number">使用者名稱不可以以數字開頭。</string>

Some files were not shown because too many files have changed in this diff Show more