cautpartener / client / src / app / locales / en.ts
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
import type { ro } from "./ro";

/** English — key parity with ro.ts is enforced by the `satisfies` check. */
export const en = {
  appName: "Caut Partener",
  tabFeed: "Feed",
  tabChats: "Chats",
  tabContacts: "Contacts",
  tabProfile: "Profile",
  statusChecking: "checking connection…",
  statusOnline: "connected",
  statusOffline: "offline",
  comingSoonTitle: "Coming soon",
  comingSoonBody: "This section arrives in a later milestone.",
  panelFeedHint: "Public posts and your contacts' posts will appear here.",
  panelChatsHint: "Your end-to-end encrypted conversations will appear here.",
  panelContactsHint: "Your verified contacts will appear here.",
  panelProfileHint: "You will create and manage your profile here.",
  backHome: "Back to home",
  updateReady: "A new version ({version}) is ready — tap to update",
  updateReadyGeneric: "A new version is ready — tap to update",
  updating: "updating…",
  versionLabel: "version {version}",
  langToggle: "Change language",

  // identity onboarding
  onbTitle: "Your identity",
  onbIntro:
    "Caut Partener has no accounts and no central servers. Your identity is a secret key kept only on your devices — create one now or bring it from another device.",
  onbCreate: "Create identity",
  onbImport: "I already have an identity",
  onbCreateImportHint: "Used Caut Partener on this device before? Import your identity instead of creating a new one.",
  onbPersistWarn:
    "The browser may evict this app's data after a period of disuse — your saved secret key is the only way to recover.",
  onbSeedTitle: "Your secret key",
  onbSeedExplain:
    "This key IS your identity. Save it now into a password manager or write it on paper — if you lose this device, the key is the only way to recover your identity. Nobody can reset it.",
  onbFingerprintHint: "your identity fingerprint — these 4 emoji identify you visually",
  copySeed: "Copy key",
  copied: "Copied",
  copyFailed: "Copy failed — copy it by hand",
  onbSavedCheck: "I saved the key somewhere safe",
  onbLast4Label: "Type the last 4 characters of the key",
  onbLast4Bad: "The characters don't match the key above",
  onbContinue: "Continue",
  onbPersistFailed: "Saving failed — try again",
  onbImportTitle: "Import your identity",
  onbImportExplain:
    "Paste the 43-character key saved on your other device, or scan its QR code. Then check that the emoji fingerprint is the one you expect.",
  onbImportPlaceholder: "identity key (43 characters)…",
  onbImportInvalid: "That's not a valid key — it has exactly 43 characters (letters, digits, - and _).",
  onbImportConfirm: "Use this identity",
  onbScanQr: "Scan QR code",
  onbScanFail: "The camera could not be started",
  back: "Back",

  // wiped-storage recovery
  recTitle: "Storage was cleared",
  recBody:
    "The browser deleted this app's data — on iOS this happens after a period of not using the site. Your identity is NOT lost if you saved your secret key: import the key and everything re-syncs.",
  recImport: "Import your saved key",
  recStartOver: "I no longer have the key — start a new identity",
  recStartOverConfirm:
    "Without the key, the old identity, its contacts and its profile cannot be recovered. Are you sure you want to start over?",
  recStartOverYes: "Yes, start over",
  cancel: "Cancel",
  closeSheet: "Close",

  // profile tab
  profIdentityTitle: "Your identity",
  profNoIdentity: "No identity on this device.",
  profPub: "Public key",
  profLink: "Your profile link",
  copyLink: "Copy link",
  linkCopied: "Link copied",
  profExport: "Export identity",
  profExportWarn:
    "The secret key will be shown on screen (text and QR code). Shield the screen from prying eyes — anyone who sees the key becomes you.",
  profExportShow: "Show the key",
  profLang: "Language",
  profSync: "Sync between devices",
  syncStarting: "sync is starting…",
  syncNoMailbox: "the sync service is not available",
  syncQueued: "{n} operations waiting",
  syncUpToDate: "everything sent",
  syncLastReplay: "last received: {n} operations at {time}",
  syncNoReplayYet: "nothing received from other devices yet",
  syncGap: "Incomplete history — some data from your other devices expired before it could sync.",
  syncPersistOn: "persistent storage active",
  syncPersistOff: "persistent storage denied by the browser",
  syncPersistUnknown: "persistent storage unknown",

  // contacts tab (M2)
  ctStarting: "contacts are loading — sync is starting…",
  ctSearchPh: "search contacts…",
  ctEmpty: "No contacts yet. Invite someone or accept an invite you received.",
  ctNoMatch: "No contact matches your search.",
  ctInvite: "Invite a contact",
  ctReceived: "I received an invite",
  ctPendingTitle: "Pending invites",
  ctPendingHint: "Whoever opens the link appears here for confirmation.",
  ctPendingExpires: "expires in {time}",
  ctExpDh: "{d}d {h}h",
  ctExpH: "{h}h",
  ctExpSoon: "under an hour",
  ctInviteNameLabel: "Your name, as the contact will see it",
  ctInviteNamePh: "your name…",
  ctInviteTitle: "Contact invite",
  ctInviteExplain:
    "Send this link to your contact (or show them the QR code). Several people can accept it, and it expires in {hours} hours.",
  ctInviteFailed: "The invite could not be published — check your connection and try again.",
  ctPasteTitle: "Accept an invite",
  ctPasteExplain: "Paste the invite link you received, or scan your contact's QR code.",
  ctPastePh: "https://cautpartener.ro/#/add/…",
  ctPasteInvalid: "That is not a valid invite link.",
  ctOpenInvite: "Open the invite",
  ctGroupsTitle: "Groups",
  ctGroupCreate: "New group",
  ctGroupNamePh: "group name…",
  ctGroupMembers: "{n} members",
  ctGroupRename: "Rename",
  ctGroupDelete: "Delete group",
  ctGroupDeleteConfirm: "The group “{name}” will be deleted. Its contacts stay in your address book.",
  ctGroupEmpty: "No members yet — tick this group in a contact's details.",
  ctNoGroups: "No groups yet. Groups let you share your profile and posts selectively.",
  ctDetailGroups: "Contact's groups",
  ctAllContactsChip: "automatic",
  ctAllContactsAuto: "automatic group — follows your contact book; no manual tick",
  ctAllContactsNote:
    "\"All contacts\" is maintained automatically: it follows your contact book and cannot be renamed or deleted. A contact added later also receives the contacts-only content already published, and a removed one keeps what they saw — the key changes only for future content. If you want, you can manually exclude a contact from the automatic group — the exclusion is respected (they are never re-enrolled silently) and the key changes for future content.",
  ctAllContactsExclude: "Exclude from the automatic group",
  ctAllContactsExcluded: "manually excluded from the automatic group",
  ctRenamePh: "local name…",
  ctRemove: "Remove contact",
  ctRemoveConfirm: "“{name}” will be removed from your contacts, on all your devices.",
  ctDetailLink: "Profile link",
  ctSave: "Save",
  ctAdded: "Contact added",
  ctRemovedToast: "Contact removed",

  // accept-invite flow (#/add/…)
  addLoading: "looking for the invite…",
  addCompareTitle: "Verify the fingerprint",
  addCompareBody:
    "Compare the 4 emoji with your contact's — they see theirs on the Profile tab. Confirm only if they are identical; that is how you make sure nobody slipped in between you.",
  addConfirm: "Confirm — the emoji match",
  addOwnLink: "This is your own invite link — send it to a contact; you cannot accept it yourself.",
  addKnown: "{name} is already in your contacts.",
  addBadLink: "Invalid invite link.",
  addNotFound:
    "The invite was not found — it may have expired (after {hours} hours) or has not synced yet. Try again in a moment.",
  addRetry: "Search again",
  addDepositFailed: "Your contact card could not be sent — check your connection and try again.",
  addSuccess: "{name} was added to your contacts.",
  addGoContacts: "See contacts",

  // ambiguous rendezvous identity (≥2 xPubs for the same pub in one room)
  addAmbiguous:
    "The invite carries conflicting keys for the same identity — the link may be compromised. Ask the person for a fresh invite link, over another channel.",
  ctAmbiguousWarn:
    "An invite received conflicting keys for the same identity ({n}) — the link may be compromised. Ask the person for a fresh link.",
  ctPartialSync: "sync incomplete — some requests may be missing; try again later",

  // creator-side pending-invite confirmation
  inviteJoined: "{name} accepted your invite",
  ctQueuedTitle: "Pending requests",
  ctQueuedHint: "Too many requests at once — review them one by one, each with its fingerprint.",
  ctQueuedReview: "Review",
  ctQueuedDismiss: "Dismiss",

  // conversations (M3)
  chatStarting: "conversations are loading — sync is starting…",
  chatEmpty: "No conversations yet. Every verified contact is a conversation — add contacts from the Contacts tab.",
  chatStartHint: "start the conversation",
  chatYou: "me",
  chatNewGroup: "New group",
  chatGroupPick: "Pick the members (at most {n} in total, including you)",
  chatGroupTooMany: "Too many members — at most {n}, including you.",
  chatGroupNeedMembers: "You need at least one verified contact for a group.",
  chatGroupCreate: "Create the group",
  chatGroupCreated: "Group created",
  chatGroupFailed: "The group could not be created — try again.",
  chatMembers: "{n} members",
  chatGroupEvictNote:
    "Groups have fixed members in this version: whoever received the room key keeps it. To exclude someone, create a new group without them.",
  chatOpening: "opening the conversation…",
  chatUnknownConv: "The conversation was not found.",
  chatNoMessages: "No messages yet — write the first one.",
  chatMsgPh: "write a message…",
  chatSend: "Send",
  chatToday: "today",
  chatYesterday: "yesterday",
  chatHideTitle: "Delete message locally",
  chatHideBody:
    "The message disappears only from this device — other members keep their copy. A serverless network cannot retract a message that was already sent.",
  chatHide: "Delete locally",

  // media messages (M3b)
  chatAttach: "Send a photo",
  chatMic: "Voice message",
  chatImgPreviewTitle: "Send this photo?",
  chatImgFailed: "The photo could not be read — this browser does not support its format.",
  chatImgTooBig: "The photo could not be compressed enough — try another one.",
  chatImgLoading: "loading…",
  chatImgUnavailable: "the photo is not available yet — fetching it from the other members",
  chatVoiceUnsupported: "Audio recording is not available in this browser.",
  chatMicDenied: "The microphone could not be started — check the browser permission.",
  chatVoiceUnavailable: "the voice message is not available yet",
  chatRecording: "recording…",
  chatOnlineNow: "online now",

  // activity categories (public profiles, M4; feed shards, M5)
  catGeneral: "General",
  catSport: "Sports",
  catDrumetii: "Hiking",
  catCalatorii: "Travel",
  catDans: "Dance",
  catArta: "Arts & culture",
  catJocuri: "Games",
  catGastronomie: "Food & drink",
  catInvatare: "Learning",
  catMuzica: "Music",
  catAfaceri: "Business",
  catTech: "IT & tech",
  catFilmeSeriale: "Movies & series",
  catCarti: "Books",
  catVoluntariat: "Volunteering",
  catParinti: "Parenting",
  catAnimale: "Pets",
  catFoto: "Photography",
  catPartenerViata: "Life partner",

  // country-wide location (the counties themselves are proper nouns in geo.ts)
  countyRomania: "All of Romania",

  // public profile editor (Profil tab, M4)
  pfTitle: "Your public profile",
  pfHint:
    "The profile is signed with your identity and readable by anyone with the link, even while you are offline. It stays visible for {hours} hours after the last visit — the app republishes it automatically on every open.",
  pfName: "Display name",
  pfBio: "About you",
  pfBioPh: "a few lines about yourself and the activities you are looking for\u2026",
  pfCounty: "County",
  pfCountyNone: "choose a county\u2026",
  pfCats: "Activity categories",
  pfCatsMax: "You can pick at most 8 categories.",
  pfPolicy: "Profile comments",
  pfPolicyOpen: "open",
  pfPolicyOff: "off",
  pfAvatar: "Profile photo",
  pfAvatarPick: "Choose photo",
  pfAvatarRemove: "Remove",
  pfAvatarBad: "The photo could not be used \u2014 unsupported format or too large.",
  pfPublish: "Publish profile",
  pfPublishing: "publishing\u2026",
  pfPublished: "Profile published",
  pfPublishInvalid: "Fill in your name and county before publishing.",
  pfPublishFailed: "The profile was saved locally but could not be published \u2014 check your connection.",
  pfLastPublished: "published: {time}",
  pfNeverPublished: "not published yet",
  pfRepublished: "auto-republished on open",
  pfPreview: "View as a visitor",

  // public profile view (#/u/\u2026, M4)
  upLoading: "looking up the profile\u2026",
  upInvalid: "Invalid profile link.",
  upUnavailable:
    "Profile unavailable \u2014 not published yet, or the data expired: the public window is {hours} hours and renews only while the profile has visitors or its owner opens the app.",
  upSpam: "Profile unavailable \u2014 the room holds a suspicious volume of data (possible spam).",
  upNet: "The service cannot be reached \u2014 check your connection and try again.",
  upRetry: "Try again",
  upStale: "shown from cache \u2014 possibly outdated",
  upOwn: "This is your public profile, as others see it.",
  upEdit: "Edit profile",
  upMessage: "Send message",
  upCreq: "Request contact",
  upCreqSent: "request sent",
  upCreqTitle: "Request contact",
  upCreqExplain:
    "Your request (name + message) is deposited, signed, into this person's public inbox. If they accept, you become verified contacts \u2014 with fingerprint confirmation on both sides.",
  upCreqMsg: "Message (optional)",
  upCreqMsgPh: "why you would like to get in touch\u2026",
  upCreqSend: "Send request",
  upCreqOk: "Request sent",
  upCreqFailed: "The request could not be sent \u2014 check your connection.",
  upNoIdentity: "Create an identity in the app to comment or request contact.",
  upComments: "Comments",
  upNoComments: "No comments yet.",
  upCommentsOff: "The profile owner turned comments off.",
  upCommentPh: "write a comment\u2026",
  upCommentSend: "Comment",
  upCommentPosted: "Comment published",
  upCommentFailed: "The comment could not be published \u2014 check your connection.",
  upCommentsTruncated: "history truncated \u2014 older comments could no longer be read",
  upHide: "Hide",
  upBlock: "Block author",
  upModDone: "Moderation published \u2014 hidden content will no longer be renewed by you",
  upModFailed: "Moderation could not be published \u2014 check your connection.",
  upModNote:
    "Hiding cannot retract copies others already downloaded \u2014 your profile simply stops renewing them.",

  // contact requests (creq/cack, M4)
  crTitle: "Contact requests",
  crHint: "Requests received through your public profile. Verify the fingerprint with the person before accepting.",
  crAcceptTitle: "{name} wants to be your contact",
  crAckTitle: "{name} accepted your contact request",
  crAckFailed: "The confirmation could not be sent \u2014 try again.",
  crMore: "\u2026and more \u2014 only the newest requests are shown.",

  // feed (M5)
  feedLoading: "loading posts…",
  feedEmpty: "No posts in this area yet — be the first to publish!",
  feedRefresh: "Refresh",
  feedWindowNote: "the public window is {hours} hours — posts live only while someone's visits renew them",
  feedWindowEdge: "beginning of the public window — no older posts exist",
  feedTruncated: "history truncated — a suspicious volume of data (possible spam); older posts were not read",
  feedGap: "incomplete history — some posts expired before they could be read",
  feedCapped: "…and more — at most {n} posts per day from the same author are shown",
  feedLike: "Like",
  feedLiked: "you liked this",
  feedLikedNote: "you liked this — likes are signed and final (they cannot be withdrawn)",
  feedLikeFailed: "The like could not be published — check your connection.",
  feedImgUnavailable: "the photo is no longer available — it expired from the public window",
  feedShare: "Copy post link",
  feedHideForeign: "Hide (locally)",
  feedDeleteLocal: "Delete locally",
  feedDeleteNote:
    "Deleted locally — copies others downloaded may persist until the window expires ({hours} hours); a serverless network cannot retract a published post.",
  feedCompose: "New post",
  feedComposeTextPh: "what activity are you looking for or proposing…",
  feedComposeImgs: "Photos (at most 4)",
  feedComposeAddImg: "Add photos",
  feedComposeShard: "Publishing in",
  feedComposeSecond: "Also publish in a second category (optional)",
  feedComposeNone: "— current category only —",
  feedPublish: "Publish",
  feedPublishing: "publishing… {done}/{total}",
  feedPublished: "Post published",
  feedPublishInvalid: "Write something or add a photo.",
  feedPublishFailed: "The post could not be published — check your connection.",

  // post permalink (#/p/…, M5)
  pvLoading: "looking for the post…",
  pvNotFound: "Post not found — it may have expired (the public window is {hours} hours) or the link is wrong.",

  // profile posts grid (M5)
  upPosts: "Posts",
  upNoPosts: "No posts found.",
  upGridNote: "only posts found in the profile's declared categories, within the {hours}-hour public window, are shown",
  upGridPartial: "some rooms could not be read — the grid may be incomplete",

  // audience restriction (M5b) — profile editor
  pfRestrictedTitle: "Restricted sections",
  pfRestrictedHint:
    "Each section is encrypted with a contact group's key before publishing — only that group's members can read it. The presence of a restricted section is publicly visible; its content is not.",
  pfRestrictedHonesty:
    "Honesty: whoever was in the group keeps what they already received. Removing someone rotates the key for future content and re-seals the current profile, but nothing already seen can be retracted. Current members hold the group's current key.",
  pfRestrictedAdd: "Add restricted section",
  pfRestrictedNoGroups: "Create a contact group first (Contacts tab) to be able to restrict sections.",
  pfRestrictedGroup: "Visible to group",
  pfRestrictedAbout: "About me (group only)",
  pfRestrictedAboutPh: "details visible only to the group…",
  pfRestrictedContact: "Contact details (group only)",
  pfRestrictedContactPh: "phone, e-mail…",
  pfRestrictedCustomAdd: "Add field",
  pfRestrictedCustomLabelPh: "label…",
  pfRestrictedCustomValPh: "value…",
  pfRestrictedRemove: "Remove section",

  // audience restriction (M5b) — public profile + feed
  upSealedTitle: "Restricted content",
  upSealedLocked: "restricted content — visible only to a group chosen by the author",
  upSealedNote:
    "Restricted content is encrypted with group keys. Whoever was in the group keeps what they received — removal cuts access to future content, it does not retract what was already seen.",
  audVisibleToYou: "restricted — visible to you",
  feedAudLabel: "Who sees this post",
  feedAudPublic: "Public",
  feedAudNote:
    "The post is encrypted with the group's key: only members see its text. The post's existence, likes and comments stay public, and photos are not available on restricted posts.",
  feedLockedPost: "restricted post — visible only to a group chosen by the author",
  audPublicMetaNote: "restricted post: its likes and comments remain public",

  // about + honest limits (#/despre, M6)
  // activities (P1)
  feedSegPosts: "Posts",
  feedSegActs: "Activities",
  feedSegMatch: "Matches",

  // matching (P2) — pool cards + anonymous likes
  mtLoading: "looking for cards…",
  mtNet: "The pool could not be read — check your connection.",
  mtMyCardTitle: "My seeker card",
  mtMyCardNone: "You are not visible in this pool. Activate a card to appear in other people's stack.",
  mtMyCardActive: "Your card is active in this pool — anyone browsing it can see it.",
  mtMyCardRetired: "Your card has been withdrawn from this pool.",
  mtBlurbLabel: "Short blurb",
  mtBlurbPh: "what you're looking for, when, at what level…",
  mtActivate: "Activate card",
  mtEdit: "Edit",
  mtRetire: "Withdraw card",
  mtCardSaved: "Card published",
  mtCardRetiredToast: "Card withdrawn",
  mtCardFailed: "The card could not be published — check your connection.",
  mtNeedName: "Set your name first (in Profile) — it appears on the card.",
  mtCardPublicNote:
    "The card is public within the chosen pool (category + county) and lives {hours} hours from the last time you opened the app.",
  mtLikedMeOne: "1 person liked you",
  mtLikedMeMany: "{n} people liked you",
  mtLikedMeNote:
    "Likes stay anonymous until they become mutual — you cannot see who they are from. The count tallies tokens, not people: anyone can mint such deposits, so it can be artificially inflated.",
  mtSentPendingOne: "1 like sent, no answer yet",
  mtSentPending: "{n} likes sent, no answer yet",
  mtInboxPartial: "inbox scan incomplete — some likes may not be counted yet (possible spam)",
  mtStackEmpty: "No cards to show in this pool yet — come back later.",
  mtStackPartial: "partial view — this pool holds too many deposits (possible spam)",
  mtLike: "Like",
  mtSkip: "Skip",
  mtNoUndoNote: "A sent like cannot be taken back; skips are local only, with no undo.",
  mtLikeFailed: "The like could not be sent — check your connection.",
  mtLikeSent: "Like sent — it stays anonymous until it is mutual.",
  mtMutualToast: "It's a match! You can talk now.",
  mtMatchesTitle: "Your matches",
  mtNoMatches: "No matches yet.",
  mtOpenChat: "Open conversation",
  mtMatchBadge: "match",
  mtHonestNote:
    "The moment a like is deposited stays visible to the node operator, and anyone who can enumerate your card can brute-test a match with their own key. A match conversation's key comes from material minted by one of the two sides — its confidentiality means trusting the other person, like any private chat — and the node operator can infer that a match happened when both sides join a fresh room at the same time.",

  // match chat (P3)
  mcAddContact: "Add to contacts",
  mcAddIntro:
    "You send them your contact card through the encrypted conversation. The contact is established only after you both compare the emoji fingerprints.",
  mcCardSent: "Contact card sent.",
  mcCardSendFailed: "The contact card could not be sent.",
  mcCardOffer: "{name} sent you their contact card.",
  mcCardReview: "Verify and add",
  mcAlreadyContact: "You are contacts.",
  actEmpty: "No upcoming activities in this area — propose one!",
  actLoadMore: "More activities",
  actCompose: "New activity",
  actEditTitle: "Edit activity",
  actTitleLabel: "Title",
  actTitlePh: "what activity are you proposing…",
  actDescLabel: "Description",
  actDescPh: "details: level, cost, what to bring…",
  actStartLabel: "Starts at",
  actEndLabel: "Ends at",
  actLocLabel: "Location (optional)",
  actLocPh: "where you meet…",
  actVisLabel: "Who sees the activity",
  actVisPublic: "Public",
  actVisContacts: "My contacts only",
  actContactsNote:
    "\"My contacts only\" is an automatically maintained group: a contact added later also sees the contacts-only activities already published, and a removed contact keeps what they saw — the key changes only for future content.",
  actAudNote:
    "The title stays public; the description and location are encrypted with the group key. Attendance (who's going) stays public.",
  actPublish: "Publish activity",
  actSave: "Save changes",
  actPublishInvalid: "Fill in the title and a valid window (start before end, in the future).",
  actPublishFailed: "The activity could not be published — check your connection.",
  actPublished: "Activity published",
  actSaved: "Activity updated",
  actJoin: "I'm going",
  actLeave: "Not going anymore",
  actJoinFailed: "Your attendance could not be published — check your connection.",
  actGoingCount: "{n} going",
  actGoingNone: "no one going yet",
  actCancelledBadge: "cancelled",
  actLockedNote: "restricted activity — details visible only to a group chosen by the organizer",
  actJoinPublicNote: "restricted activity: attendance (who's going) stays public",
  actOrganizer: "organizer",
  actEdit: "Edit",
  actCancelAct: "Cancel activity",
  actCancelConfirm: "The activity \"{title}\" will be marked as cancelled for everyone. Attendance stays visible.",
  actCancelYes: "Yes, cancel it",
  actCancelledToast: "Activity cancelled",
  actDetailGoing: "Attendees",
  actGoingMore: "…and {n} more",
  actShare: "Copy activity link",
  actWhen: "When",
  actWhere: "Where",

  // activity permalink (#/a/…, P1)
  avLoading: "looking for the activity…",
  avNotFound: "The activity was not found — it may have expired (the public window is {hours} hours) or the link is wrong.",

  // profile upcoming-activities strip (P1)
  upActs: "Upcoming activities",
  upNoActs: "No upcoming activities found.",

  aboutLink: "About the app and its limits",
  aboutTitle: "About Caut Partener",
  aboutWhatTitle: "What it is",
  aboutIntro:
    "Caut Partener is a peer-to-peer social network with no application servers: profiles, posts and messages travel directly between members' devices or through a temporary relay mailbox that cannot read private conversations. There are no accounts and no central database — nothing is stored permanently on anyone's servers, and the source code is public.",
  aboutLimitsTitle: "Honest limits",
  aboutLimitsIntro: "A serverless network makes trade-offs that classic apps hide. These are ours, in the open:",
  aboutLimWindow:
    "The public window is {hours} hours: public profiles and posts live only while somebody renews them through visits — there is no permanent archive.",
  aboutLimFeeds:
    "Feeds are best-effort: neither global nor consistent — you see what recent visitors renewed, not “everything that exists”.",
  aboutLimRetract:
    "Deletion is not retraction: a post or message already downloaded by others cannot be recalled; deleting only stops your side from renewing it.",
  aboutLimSpam:
    "Public rooms are open, so spam defense exists only inside your app: render caps, local blocklists and moderation of your own profile.",
  aboutLimOperator:
    "The relay node operator can read public content (signed but not encrypted) and can map public rooms to identities. Private rooms stay opaque: the node sees only sizes and timing.",
  aboutLimSybil:
    "Identities are free and unlimited (Sybil attacks are possible) — trust comes from comparing emoji fingerprints, from “first key wins” (TOFU) and from verified contacts, not from an authority.",
  aboutLimForward:
    "No forward secrecy in 1:1 conversations or within a group epoch: a compromised key exposes the history of that room or epoch.",
  aboutLimRestricted:
    "For restricted posts, the post's existence, likes and comments stay public — only the content is encrypted for the chosen group.",
  aboutLimMatching:
    "In matching, your seeker card is public within the chosen pool. A sent like is encrypted and anonymous until it becomes mutual, but it cannot be withdrawn, the moment of deposit stays visible to the operator, and anyone who can enumerate your card can brute-test with their own key whether you liked them.",
  aboutLimActivities:
    "For restricted activities, the title, time window and attendance (who's going) stay public — only the description and location are encrypted. \"My contacts only\" is an automatically maintained group: a contact added later also sees activities already published, and a removed one keeps what they saw.",
  aboutLimCalls:
    "Audio/video calls are 1-to-1 only and only work while you are both online at the same time — there are no group calls and no voicemail. Sound and video flow encrypted directly between devices (or through a TURN relay, which cannot read them but does see IP addresses and data volume). A missed call leaves only a marker in the conversation.",
  aboutUpdatesTitle: "Version and updates",
  aboutUpdateHint:
    "The app checks for new versions periodically and whenever you return to it; when one is ready, the update banner appears.",
  aboutCheckUpdates: "Check for updates",
  aboutCheckStarted: "Checking — if a new version exists, the update banner will appear.",
  aboutSourceTitle: "Source code",
  aboutSourceBody: "The app is open source (MIT license). The complete code can be read and cloned from:",

  // calls (P4)
  callAudio: "Audio call",
  callVideo: "Video call",
  callNeedOnline: "Calls only work while you are both online at the same time — the contact is not online right now.",
  callIncoming: "{name} is calling you",
  callIncomingVideo: "{name} is calling you with video",
  callAcceptAudio: "Answer",
  callAcceptVideo: "Answer with video",
  callDecline: "Decline",
  callRinging: "Ringing…",
  callConnecting: "Connecting…",
  callDeclinedToast: "Call declined.",
  callBusyToast: "Busy — already in a call.",
  callMissedToast: "No answer.",
  callEndedToast: "Call ended.",
  callFailedToast: "The call could not connect — check both connections.",
  callPermDenied: "Microphone/camera access denied — the call needs this permission.",
  callNoDevice: "No microphone or camera found on this device.",
  callDeviceBusy: "The microphone or camera is in use by another app.",
  callRecoverFailed: "The microphone/camera could not be restarted — the other side can no longer hear you. Hang up and call again.",
  callHangup: "Hang up",
  callMicToggle: "Microphone",
  callCamToggle: "Camera",
  callFlip: "Flip camera",
  callMissedLine: "Missed call",
  callEndedLine: "Call ended",
  callEndedLineDur: "Call ended · {dur}",
  callP2PNote: "The call flows directly between you (encrypted); a TURN relay can only see IP addresses and data volume.",

  // TOFU key-change warning
  tofuTitle: "Warning: the contact's key changed",
  tofuBody:
    "{name}'s encryption key differs from the one you have saved. This happens when they recreate their identity — but it can also mean someone else is impersonating them. Check with the contact directly, over another channel, before accepting.",
  tofuAccept: "Accept the new key",
  tofuReject: "Reject",
  tofuRejected: "The new key was rejected — the contact stays unchanged.",
} satisfies Record<keyof typeof ro, string>;

static mirror of HEAD · about · clone: git clone https://cod-sursa.cautpartener.ro/cautpartener.git