create-mithaq 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/LICENSES/openstatus-time-picker.txt +21 -0
  2. package/index.js +457 -63
  3. package/package.json +1 -1
  4. package/template/apps/{asas-platform → asas-backoffice}/package.json +5 -3
  5. package/template/apps/{asas-platform → asas-backoffice}/src/api.ts +1 -1
  6. package/template/apps/{asas-platform → asas-backoffice}/src/auth-frame.tsx +12 -12
  7. package/template/apps/asas-backoffice/src/components.ts +35 -0
  8. package/template/apps/asas-backoffice/src/first-user.ts +14 -0
  9. package/template/apps/{asas-platform → asas-backoffice}/src/links.ts +1 -1
  10. package/template/apps/{asas-platform → asas-backoffice}/src/locale-switch.tsx +7 -5
  11. package/template/apps/asas-backoffice/src/no-organization.tsx +5 -0
  12. package/template/apps/asas-backoffice/src/redirect.ts +1 -0
  13. package/template/apps/{asas-platform → asas-backoffice}/src/request-headers.ts +1 -1
  14. package/template/apps/asas-backoffice/src/routeTree.gen.ts +206 -0
  15. package/template/apps/asas-backoffice/src/router.tsx +18 -0
  16. package/template/apps/asas-backoffice/src/routes/(screens)/backoffice/files.ts +10 -0
  17. package/template/apps/{asas-platform/src/routes/(screens)/platform → asas-backoffice/src/routes/(screens)/backoffice}/screen.json +426 -64
  18. package/template/apps/{asas-platform/src/routes/(screens)/platform → asas-backoffice/src/routes/(screens)/backoffice}/types.ts +18 -0
  19. package/template/apps/{asas-platform/src/routes/(screens)/platform.$.tsx → asas-backoffice/src/routes/(screens)/backoffice.$.tsx} +4 -4
  20. package/template/apps/{asas-platform/src/routes/(screens)/platform.index.tsx → asas-backoffice/src/routes/(screens)/backoffice.index.tsx} +3 -3
  21. package/template/apps/{asas-platform/src/routes/(screens)/platform.tsx → asas-backoffice/src/routes/(screens)/backoffice.tsx} +3 -3
  22. package/template/apps/{asas-platform → asas-backoffice}/src/routes/__root.tsx +2 -2
  23. package/template/apps/asas-backoffice/src/routes/api/$.ts +34 -0
  24. package/template/apps/asas-backoffice/src/routes/auth.create-first-user.tsx +113 -0
  25. package/template/apps/{asas-platform → asas-backoffice}/src/routes/auth.sign-in.tsx +9 -4
  26. package/template/apps/{asas-platform → asas-backoffice}/src/routes/unauthorized.tsx +24 -12
  27. package/template/apps/{asas-platform → asas-backoffice}/src/screen-form.tsx +5 -3
  28. package/template/apps/{asas-platform → asas-backoffice}/src/screen-list.tsx +6 -5
  29. package/template/apps/{asas-platform → asas-backoffice}/src/screen-names.ts +1 -1
  30. package/template/apps/{asas-platform → asas-backoffice}/src/screens.tsx +46 -26
  31. package/template/apps/asas-backoffice/src/server.ts +30 -0
  32. package/template/apps/{asas-platform → asas-backoffice}/src/slide.ts +1 -1
  33. package/template/apps/{asas-platform → asas-backoffice}/src/start.ts +1 -1
  34. package/template/apps/{asas-platform → asas-backoffice}/src/styles.css +9 -7
  35. package/template/apps/{asas-platform → asas-backoffice}/tsconfig.json +2 -2
  36. package/template/apps/{asas-platform → asas-backoffice}/vite.config.ts +7 -1
  37. package/template/apps/asas-tanstack/package.json +4 -2
  38. package/template/apps/asas-tanstack/src/api.ts +1 -1
  39. package/template/apps/asas-tanstack/src/auth-frame.tsx +12 -12
  40. package/template/apps/asas-tanstack/src/choose-password.tsx +150 -0
  41. package/template/apps/asas-tanstack/src/components.ts +35 -0
  42. package/template/apps/asas-tanstack/src/copy.ts +416 -79
  43. package/template/apps/asas-tanstack/src/first-run.ts +16 -0
  44. package/template/apps/asas-tanstack/src/links.ts +1 -1
  45. package/template/apps/asas-tanstack/src/locale-switch.tsx +7 -5
  46. package/template/apps/asas-tanstack/src/no-organization.tsx +108 -0
  47. package/template/apps/asas-tanstack/src/redirect.ts +20 -1
  48. package/template/apps/asas-tanstack/src/request-headers.ts +1 -1
  49. package/template/apps/asas-tanstack/src/routeTree.gen.ts +42 -0
  50. package/template/apps/asas-tanstack/src/router.tsx +12 -1
  51. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/files.ts +9 -2
  52. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/screen.json +475 -116
  53. package/template/apps/asas-tanstack/src/routes/(screens)/dashboard/types.ts +4 -2
  54. package/template/apps/asas-tanstack/src/routes/(screens)/portal/files.ts +7 -2
  55. package/template/apps/asas-tanstack/src/routes/(screens)/portal/screen.json +83 -319
  56. package/template/apps/asas-tanstack/src/routes/(screens)/portal/types.ts +3 -1
  57. package/template/apps/asas-tanstack/src/routes/__root.tsx +1 -1
  58. package/template/apps/asas-tanstack/src/routes/auth.accept-invitation.tsx +14 -7
  59. package/template/apps/asas-tanstack/src/routes/auth.first-run.tsx +114 -0
  60. package/template/apps/asas-tanstack/src/routes/auth.forgot-password.tsx +2 -2
  61. package/template/apps/asas-tanstack/src/routes/auth.handoff.ts +5 -5
  62. package/template/apps/asas-tanstack/src/routes/auth.reset-password.tsx +7 -100
  63. package/template/apps/asas-tanstack/src/routes/auth.set-password.tsx +16 -0
  64. package/template/apps/asas-tanstack/src/routes/auth.sign-in.tsx +27 -15
  65. package/template/apps/asas-tanstack/src/routes/auth.sign-up.tsx +59 -38
  66. package/template/apps/asas-tanstack/src/routes/index.tsx +18 -14
  67. package/template/apps/asas-tanstack/src/routes/unauthorized.tsx +24 -12
  68. package/template/apps/asas-tanstack/src/screen-form.tsx +5 -3
  69. package/template/apps/asas-tanstack/src/screen-list.tsx +6 -5
  70. package/template/apps/asas-tanstack/src/screen-names.ts +1 -1
  71. package/template/apps/asas-tanstack/src/screens.tsx +46 -26
  72. package/template/apps/asas-tanstack/src/server.ts +10 -0
  73. package/template/apps/asas-tanstack/src/slide.ts +1 -1
  74. package/template/apps/asas-tanstack/src/styles.css +7 -3
  75. package/template/apps/asas-tanstack/tsconfig.json +2 -2
  76. package/template/apps/asas-tanstack/vite.config.ts +7 -1
  77. package/template/package.json +3 -3
  78. package/template/templates/asas/.claude/skills/add-field/SKILL.md +27 -0
  79. package/template/templates/asas/.claude/skills/add-kayan/SKILL.md +26 -0
  80. package/template/templates/asas/.claude/skills/eject-admin-piece/SKILL.md +28 -0
  81. package/template/templates/asas/.claude/skills/upgrade-mithaq/SKILL.md +35 -0
  82. package/template/templates/asas/.env.example +22 -5
  83. package/template/templates/asas/AGENTS.md +101 -0
  84. package/template/templates/asas/CLAUDE.md +1 -0
  85. package/template/templates/asas/README.md +1056 -0
  86. package/template/templates/asas/package.json +19 -4
  87. package/template/templates/asas/src/asas.ts +81 -56
  88. package/template/templates/asas/src/{platform-http.ts → backoffice-http.ts} +48 -27
  89. package/template/templates/asas/src/dev-setup.ts +112 -0
  90. package/template/templates/asas/src/local-database.ts +22 -0
  91. package/template/templates/asas/src/messages/account.json +129 -103
  92. package/template/templates/asas/src/messages/auditLog.json +223 -105
  93. package/template/templates/asas/src/messages/backofficeLog.json +132 -0
  94. package/template/templates/asas/src/messages/courses.json +256 -214
  95. package/template/templates/asas/src/messages/enrollments.json +45 -27
  96. package/template/templates/asas/src/messages/invitation.json +72 -56
  97. package/template/templates/asas/src/messages/localUsers.json +34 -28
  98. package/template/templates/asas/src/messages/media.json +31 -21
  99. package/template/templates/asas/src/messages/member.json +59 -39
  100. package/template/templates/asas/src/messages/memberRoles.json +37 -31
  101. package/template/templates/asas/src/messages/mithaq.json +206 -162
  102. package/template/templates/asas/src/messages/organization.json +63 -41
  103. package/template/templates/asas/src/messages/organizationRole.json +59 -47
  104. package/template/templates/asas/src/messages/roles.json +59 -45
  105. package/template/templates/asas/src/messages/session.json +99 -79
  106. package/template/templates/asas/src/messages/sessions.json +110 -80
  107. package/template/templates/asas/src/messages/settings.json +51 -53
  108. package/template/templates/asas/src/messages/tags.json +31 -21
  109. package/template/templates/asas/src/messages/user.json +109 -87
  110. package/template/templates/asas/src/messages/verification.json +59 -47
  111. package/template/templates/asas/src/migrations/20261014221355_tags_restrict/migration.sql +1 -0
  112. package/template/templates/asas/src/migrations/20261014221355_tags_restrict/snapshot.json +3881 -0
  113. package/template/templates/asas/src/migrations/20261014222218_one_seat_per_session/migration.sql +1 -0
  114. package/template/templates/asas/src/migrations/20261014222218_one_seat_per_session/snapshot.json +3894 -0
  115. package/template/templates/asas/src/migrations/20261014223522_session_archive_reason/migration.sql +1 -0
  116. package/template/templates/asas/src/migrations/20261014223522_session_archive_reason/snapshot.json +3907 -0
  117. package/template/templates/asas/src/migrations/20261014225728_course_archive_by_operation/migration.sql +1 -0
  118. package/template/templates/asas/src/migrations/20261014225728_course_archive_by_operation/snapshot.json +3907 -0
  119. package/template/templates/asas/src/migrations/20261015042444_organization_names/migration.sql +1 -0
  120. package/template/templates/asas/src/migrations/20261015042444_organization_names/snapshot.json +3920 -0
  121. package/template/templates/asas/src/migrations/20261015053212_member_last_seen/migration.sql +1 -0
  122. package/template/templates/asas/src/migrations/20261015053212_member_last_seen/snapshot.json +3933 -0
  123. package/template/templates/asas/src/migrations/20261015093203_activity_and_staff_history/migration.sql +15 -0
  124. package/template/templates/asas/src/migrations/20261015093203_activity_and_staff_history/snapshot.json +4113 -0
  125. package/template/templates/asas/src/migrations/20261015095752_portal/migration.sql +2 -0
  126. package/template/templates/asas/src/migrations/20261015095752_portal/snapshot.json +4113 -0
  127. package/template/templates/asas/src/migrations/20261015135529_better_auth_indexes/migration.sql +9 -0
  128. package/template/templates/asas/src/migrations/20261015135529_better_auth_indexes/snapshot.json +4302 -0
  129. package/template/templates/asas/src/migrations/20261015162438_audit_changes_json/migration.sql +27 -0
  130. package/template/templates/asas/src/migrations/20261015162438_audit_changes_json/snapshot.json +4289 -0
  131. package/template/templates/asas/src/migrations/schema.ts +1 -0
  132. package/template/templates/asas/src/mithaq.ts +4 -406
  133. package/template/templates/asas/src/organizations.ts +3 -0
  134. package/template/templates/asas/src/seed.ts +147 -45
  135. package/template/templates/asas/src/{ui-platform → ui-backoffice}/home.tsx +9 -9
  136. package/template/templates/asas/src/ui-backoffice/organization-actions.tsx +121 -0
  137. package/template/templates/asas/src/{ui-platform → ui-backoffice}/record-page.tsx +4 -4
  138. package/template/templates/asas/src/{ui-platform → ui-backoffice}/role-cells.tsx +13 -6
  139. package/template/templates/asas/src/{ui-platform → ui-backoffice}/user-actions.tsx +46 -48
  140. package/template/templates/asas/src/ui-dashboard/dashboard.tsx +10 -4
  141. package/template/templates/asas/src/ui-dashboard/home.tsx +73 -0
  142. package/template/templates/asas/src/ui-dashboard/members-card.tsx +147 -0
  143. package/template/templates/asas/src/ui-dashboard/members.tsx +637 -438
  144. package/template/templates/asas/src/ui-dashboard/versions.tsx +22 -8
  145. package/template/templates/asas/src/ui-portal/courses-home.tsx +96 -0
  146. package/template/templates/asas/src/ui-portal/home.tsx +9 -13
  147. package/template/templates/features/audit-log/audit-log/audit-log.ts +284 -0
  148. package/template/templates/features/audit-log/backoffice-log/backoffice-log.ts +67 -0
  149. package/template/templates/features/audit-log/index.ts +4 -0
  150. package/template/templates/features/audit-log/package.json +13 -0
  151. package/template/templates/features/auth/index.ts +11 -0
  152. package/template/templates/{asas/src → features/auth}/local-users/local-users.ts +7 -7
  153. package/template/templates/{auth → features/auth}/package.json +2 -3
  154. package/template/templates/{asas/src/kayan → features}/auth/user/account.access.ts +2 -2
  155. package/template/templates/{asas/src/kayan → features}/auth/user/account.ts +2 -2
  156. package/template/templates/{asas/src/kayan → features}/auth/user/invitation.access.ts +1 -1
  157. package/template/templates/{asas/src/kayan → features}/auth/user/invitation.ts +3 -3
  158. package/template/templates/{asas/src/kayan → features}/auth/user/member.access.ts +4 -4
  159. package/template/templates/{asas/src/kayan → features}/auth/user/member.screens.ts +5 -5
  160. package/template/templates/{asas/src/kayan → features}/auth/user/member.ts +4 -3
  161. package/template/templates/{asas/src/kayan → features}/auth/user/organization.access.ts +6 -6
  162. package/template/templates/{asas/src/kayan → features}/auth/user/organization.screens.ts +6 -6
  163. package/template/templates/{asas/src/kayan → features}/auth/user/organization.ts +2 -1
  164. package/template/templates/{asas/src/kayan → features}/auth/user/organizationRole.access.ts +2 -2
  165. package/template/templates/{asas/src/kayan → features}/auth/user/organizationRole.ts +3 -3
  166. package/template/templates/{asas/src/kayan → features}/auth/user/session.access.ts +2 -2
  167. package/template/templates/{asas/src/kayan → features}/auth/user/session.ts +2 -2
  168. package/template/templates/{asas/src/kayan → features}/auth/user/user.access.ts +8 -7
  169. package/template/templates/{asas/src/kayan → features}/auth/user/user.screens.ts +9 -9
  170. package/template/templates/{asas/src/kayan → features}/auth/user/user.ts +2 -2
  171. package/template/templates/{asas/src/kayan → features}/auth/user/verification.access.ts +2 -2
  172. package/template/templates/{asas/src/kayan → features}/auth/user/verification.ts +2 -2
  173. package/template/templates/{asas/src → features/courses}/courses/courses.ts +122 -133
  174. package/template/templates/features/courses/courses/cover-reads.ts +33 -0
  175. package/template/templates/features/courses/courses/ui/archived-badge.tsx +9 -0
  176. package/template/templates/{asas/src → features/courses}/courses/ui/fee-cell.tsx +1 -1
  177. package/template/templates/{asas/src → features/courses}/courses/ui/status-cell.tsx +5 -3
  178. package/template/templates/features/courses/courses/ui/unpublish-button.tsx +15 -0
  179. package/template/templates/features/courses/domain/archive.ts +39 -0
  180. package/template/templates/features/courses/domain/on-offer.ts +6 -0
  181. package/template/templates/features/courses/enrollments/enrollments.ts +309 -0
  182. package/template/templates/{asas/src → features/courses}/enrollments/ui/session-cell.tsx +17 -7
  183. package/template/templates/{asas/src → features/courses/enrollments}/ui/session-labels.tsx +3 -3
  184. package/template/templates/features/courses/index.ts +8 -0
  185. package/template/templates/features/courses/package.json +24 -0
  186. package/template/templates/features/courses/portal/ui/catalogue.tsx +86 -0
  187. package/template/templates/features/courses/portal/ui/course.tsx +176 -0
  188. package/template/templates/features/courses/portal/ui/enrollments.tsx +170 -0
  189. package/template/templates/features/courses/portal/ui/parts.tsx +95 -0
  190. package/template/templates/features/courses/portal/ui/reads.ts +76 -0
  191. package/template/templates/features/courses/portal/ui/rules.ts +87 -0
  192. package/template/templates/features/courses/portal/ui/shell.tsx +194 -0
  193. package/template/templates/features/courses/sessions/seats.ts +42 -0
  194. package/template/templates/{asas/src → features/courses}/sessions/sessions.ts +45 -14
  195. package/template/templates/features/media/index.ts +10 -0
  196. package/template/templates/{asas/src → features/media}/media/media.ts +34 -40
  197. package/template/templates/{asas/src → features/media}/media/ui/preview.tsx +2 -2
  198. package/template/templates/features/media/package.json +19 -0
  199. package/template/templates/features/roles/index.ts +4 -0
  200. package/template/templates/{asas/src → features/roles}/member-roles/member-roles.ts +15 -15
  201. package/template/templates/features/roles/package.json +21 -0
  202. package/template/templates/{asas/src → features/roles}/roles/roles.ts +23 -23
  203. package/template/templates/{asas/src → features/roles}/roles/ui/role-form.tsx +65 -24
  204. package/template/templates/{asas/src → features/roles}/roles/ui/roles.tsx +49 -25
  205. package/template/templates/features/settings/index.ts +3 -0
  206. package/template/templates/features/settings/package.json +13 -0
  207. package/template/templates/{asas/src → features/settings}/settings/settings.ts +12 -17
  208. package/template/templates/features/tags/index.ts +3 -0
  209. package/template/templates/features/tags/package.json +14 -0
  210. package/template/templates/{asas/src → features/tags}/tags/tags.ts +15 -15
  211. package/template/templates/mithaq/package.json +33 -0
  212. package/template/templates/mithaq/src/mail/index.ts +32 -0
  213. package/template/templates/mithaq/src/mail/mailers.ts +36 -0
  214. package/template/templates/{asas/src/audit-log/sink.ts → mithaq/src/mithaq/audit-sink.ts} +31 -9
  215. package/template/templates/{auth/src/platform-roles.ts → mithaq/src/mithaq/auth/backoffice-roles.ts} +44 -23
  216. package/template/templates/mithaq/src/mithaq/auth/index.ts +1859 -0
  217. package/template/templates/mithaq/src/mithaq/auth/organizations.ts +80 -0
  218. package/template/templates/{asas/src → mithaq/src/mithaq}/pages.ts +16 -7
  219. package/template/templates/mithaq/src/mithaq/rich-text-nodes.ts +19 -0
  220. package/template/templates/mithaq/src/mithaq.ts +593 -0
  221. package/template/templates/mithaq/src/roles/default-member-roles.ts +29 -0
  222. package/template/templates/{asas → mithaq}/src/roles/grants.ts +1 -1
  223. package/template/templates/mithaq/src/roles/member-access.ts +11 -0
  224. package/template/templates/{asas/src → mithaq/src/roles}/statements.ts +6 -4
  225. package/template/templates/mithaq/src/roles/system-roles.ts +16 -0
  226. package/template/templates/{asas/src → mithaq/src/roles}/tenant-access.ts +96 -57
  227. package/template/templates/{asas/src → mithaq/src/roles}/tenant-roles.ts +84 -25
  228. package/template/templates/{asas → mithaq}/src/steps/expected-updated-at.ts +2 -2
  229. package/template/templates/{asas → mithaq}/src/storage/index.ts +3 -1
  230. package/template/templates/mithaq/src/ui/activity-page.tsx +33 -0
  231. package/template/templates/mithaq/src/ui/activity.tsx +527 -0
  232. package/template/templates/{asas → mithaq}/src/ui/app-shell.tsx +27 -19
  233. package/template/templates/mithaq/src/ui/rich-text-nodes.tsx +33 -0
  234. package/template/templates/{asas → mithaq}/src/ui/screen-nav.tsx +1 -1
  235. package/template/templates/{asas → mithaq}/src/ui/top-shell.tsx +8 -8
  236. package/template/apps/asas-platform/src/components/mithaq/cells.tsx +0 -112
  237. package/template/apps/asas-platform/src/components/mithaq/controls.tsx +0 -650
  238. package/template/apps/asas-platform/src/components/mithaq/field-shell.tsx +0 -53
  239. package/template/apps/asas-platform/src/components/mithaq/filter-operators.ts +0 -108
  240. package/template/apps/asas-platform/src/components/mithaq/form.tsx +0 -1467
  241. package/template/apps/asas-platform/src/components/mithaq/list-columns.tsx +0 -108
  242. package/template/apps/asas-platform/src/components/mithaq/list-filters.tsx +0 -368
  243. package/template/apps/asas-platform/src/components/mithaq/list-pagination.tsx +0 -153
  244. package/template/apps/asas-platform/src/components/mithaq/list-query.ts +0 -432
  245. package/template/apps/asas-platform/src/components/mithaq/list-selection.tsx +0 -74
  246. package/template/apps/asas-platform/src/components/mithaq/list.tsx +0 -865
  247. package/template/apps/asas-platform/src/components/mithaq/nav.tsx +0 -871
  248. package/template/apps/asas-platform/src/components/mithaq/nodes.tsx +0 -1024
  249. package/template/apps/asas-platform/src/components/mithaq/relationship-cell.tsx +0 -127
  250. package/template/apps/asas-platform/src/components/mithaq/relationship-control.tsx +0 -400
  251. package/template/apps/asas-platform/src/components/mithaq/rich-text-nodes.ts +0 -6
  252. package/template/apps/asas-platform/src/components/mithaq/rich-text.tsx +0 -1083
  253. package/template/apps/asas-platform/src/components/mithaq/upload.tsx +0 -686
  254. package/template/apps/asas-platform/src/components/ui/drawer.tsx +0 -216
  255. package/template/apps/asas-platform/src/redirect.ts +0 -1
  256. package/template/apps/asas-platform/src/routeTree.gen.ts +0 -181
  257. package/template/apps/asas-platform/src/router.tsx +0 -7
  258. package/template/apps/asas-platform/src/routes/(screens)/platform/files.ts +0 -4
  259. package/template/apps/asas-platform/src/routes/api/$.ts +0 -30
  260. package/template/apps/asas-platform/src/server.ts +0 -20
  261. package/template/apps/asas-tanstack/src/components/mithaq/cells.tsx +0 -112
  262. package/template/apps/asas-tanstack/src/components/mithaq/controls.tsx +0 -650
  263. package/template/apps/asas-tanstack/src/components/mithaq/field-shell.tsx +0 -53
  264. package/template/apps/asas-tanstack/src/components/mithaq/filter-operators.ts +0 -108
  265. package/template/apps/asas-tanstack/src/components/mithaq/form.tsx +0 -1467
  266. package/template/apps/asas-tanstack/src/components/mithaq/list-columns.tsx +0 -108
  267. package/template/apps/asas-tanstack/src/components/mithaq/list-filters.tsx +0 -368
  268. package/template/apps/asas-tanstack/src/components/mithaq/list-pagination.tsx +0 -153
  269. package/template/apps/asas-tanstack/src/components/mithaq/list-query.ts +0 -432
  270. package/template/apps/asas-tanstack/src/components/mithaq/list-selection.tsx +0 -74
  271. package/template/apps/asas-tanstack/src/components/mithaq/list.tsx +0 -865
  272. package/template/apps/asas-tanstack/src/components/mithaq/nav.tsx +0 -871
  273. package/template/apps/asas-tanstack/src/components/mithaq/nodes.tsx +0 -1024
  274. package/template/apps/asas-tanstack/src/components/mithaq/relationship-cell.tsx +0 -127
  275. package/template/apps/asas-tanstack/src/components/mithaq/relationship-control.tsx +0 -400
  276. package/template/apps/asas-tanstack/src/components/mithaq/rich-text-nodes.ts +0 -6
  277. package/template/apps/asas-tanstack/src/components/mithaq/rich-text.tsx +0 -1083
  278. package/template/apps/asas-tanstack/src/components/mithaq/upload.tsx +0 -686
  279. package/template/apps/asas-tanstack/src/components/ui/drawer.tsx +0 -215
  280. package/template/templates/asas/src/audit-log/audit-log.ts +0 -187
  281. package/template/templates/asas/src/audit-log/ui/activity.tsx +0 -243
  282. package/template/templates/asas/src/courses/ui/archive-button.tsx +0 -25
  283. package/template/templates/asas/src/courses/ui/no-courses-on-offer.tsx +0 -11
  284. package/template/templates/asas/src/courses/ui/on-offer.ts +0 -6
  285. package/template/templates/asas/src/enrollments/enrollments.ts +0 -137
  286. package/template/templates/asas/src/rich-text-nodes.ts +0 -11
  287. package/template/templates/asas/src/roles.ts +0 -16
  288. package/template/templates/asas/src/ui/rich-text-nodes.tsx +0 -15
  289. package/template/templates/asas/src/ui-platform/organization-actions.tsx +0 -114
  290. package/template/templates/auth/src/index.ts +0 -690
  291. /package/template/apps/{asas-platform → asas-backoffice}/components.json +0 -0
  292. /package/template/apps/{asas-platform → asas-backoffice}/gitignore +0 -0
  293. /package/template/apps/{asas-platform → asas-backoffice}/public/favicon.svg +0 -0
  294. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/accordion.tsx +0 -0
  295. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/alert-dialog.tsx +0 -0
  296. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/avatar.tsx +0 -0
  297. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/badge.tsx +0 -0
  298. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/breadcrumb.tsx +0 -0
  299. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/button-group.tsx +0 -0
  300. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/button.tsx +0 -0
  301. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/calendar.tsx +0 -0
  302. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/card.tsx +0 -0
  303. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/chart.tsx +0 -0
  304. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/checkbox.tsx +0 -0
  305. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/collapsible.tsx +0 -0
  306. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/combobox.tsx +0 -0
  307. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/dialog.tsx +0 -0
  308. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/direction.tsx +0 -0
  309. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/directional-icon.tsx +0 -0
  310. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/dropdown-menu.tsx +0 -0
  311. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/field.tsx +0 -0
  312. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input-group.tsx +0 -0
  313. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input-otp.tsx +0 -0
  314. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/input.tsx +0 -0
  315. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/label.tsx +0 -0
  316. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/popover.tsx +0 -0
  317. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/radio-group.tsx +0 -0
  318. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/resizable.tsx +0 -0
  319. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/scroll-area.tsx +0 -0
  320. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/select.tsx +0 -0
  321. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/separator.tsx +0 -0
  322. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/sheet.tsx +0 -0
  323. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/sidebar.tsx +0 -0
  324. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/skeleton.tsx +0 -0
  325. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/slider.tsx +0 -0
  326. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/spinner.tsx +0 -0
  327. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/switch.tsx +0 -0
  328. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/table.tsx +0 -0
  329. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/tabs.tsx +0 -0
  330. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/textarea.tsx +0 -0
  331. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/toggle-group.tsx +0 -0
  332. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/toggle.tsx +0 -0
  333. /package/template/apps/{asas-platform → asas-backoffice}/src/components/ui/tooltip.tsx +0 -0
  334. /package/template/apps/{asas-platform → asas-backoffice}/src/copy.ts +0 -0
  335. /package/template/apps/{asas-platform → asas-backoffice}/src/hooks/use-mobile.ts +0 -0
  336. /package/template/apps/{asas-platform → asas-backoffice}/src/lib/utils.ts +0 -0
  337. /package/template/apps/{asas-platform → asas-backoffice}/src/theme.ts +0 -0
  338. /package/template/templates/{asas/src/kayan → features}/auth/user/index.ts +0 -0
  339. /package/template/templates/{asas/src → features/courses}/courses/ui/no-courses.tsx +0 -0
  340. /package/template/templates/{asas → mithaq}/components.json +0 -0
  341. /package/template/templates/{asas → mithaq}/src/storage/defaults.ts +0 -0
  342. /package/template/templates/{asas → mithaq}/src/storage/local.ts +0 -0
  343. /package/template/templates/{asas → mithaq}/src/storage/r2.ts +0 -0
  344. /package/template/templates/{asas → mithaq}/src/storage/s3.ts +0 -0
  345. /package/template/templates/{asas → mithaq}/src/storage/uploadthing.ts +0 -0
  346. /package/template/templates/{asas → mithaq}/src/ui/kpi-card.tsx +0 -0
  347. /package/template/templates/{asas → mithaq}/src/ui/record-page.tsx +0 -0
  348. /package/template/templates/{asas → mithaq}/src/ui/table-page.tsx +0 -0
@@ -1,15 +1,28 @@
1
1
  // Imports no router, stylesheet or React, so a test can load this under plain `node`.
2
+ import { flattenMessageFile } from 'mithaq/admin'
3
+ import { organizations, showOrganization } from 'asas/organizations'
4
+ import localUsers from 'asas/messages/localUsers.json' with { type: 'json' }
5
+ import organization from 'asas/messages/organization.json' with { type: 'json' }
6
+ import tags from 'asas/messages/tags.json' with { type: 'json' }
7
+ // add-on: audit-log
2
8
  import auditLog from 'asas/messages/auditLog.json' with { type: 'json' }
9
+ import backofficeLog from 'asas/messages/backofficeLog.json' with { type: 'json' }
10
+ // end add-on: audit-log
11
+ // add-on: courses
3
12
  import courses from 'asas/messages/courses.json' with { type: 'json' }
4
13
  import enrollments from 'asas/messages/enrollments.json' with { type: 'json' }
5
- import localUsers from 'asas/messages/localUsers.json' with { type: 'json' }
14
+ import sessions from 'asas/messages/sessions.json' with { type: 'json' }
15
+ // end add-on: courses
16
+ // add-on: media
6
17
  import media from 'asas/messages/media.json' with { type: 'json' }
7
- import organization from 'asas/messages/organization.json' with { type: 'json' }
18
+ // end add-on: media
19
+ // add-on: roles
8
20
  import memberRoles from 'asas/messages/memberRoles.json' with { type: 'json' }
9
21
  import roles from 'asas/messages/roles.json' with { type: 'json' }
10
- import sessions from 'asas/messages/sessions.json' with { type: 'json' }
22
+ // end add-on: roles
23
+ // add-on: settings
11
24
  import settings from 'asas/messages/settings.json' with { type: 'json' }
12
- import tags from 'asas/messages/tags.json' with { type: 'json' }
25
+ // end add-on: settings
13
26
 
14
27
  // Restated, not imported: the original lives in a `.tsx` file and the test runner has no JSX compiler.
15
28
  type Translate = (key: string, fallback?: string) => string | undefined
@@ -17,6 +30,11 @@ type Translate = (key: string, fallback?: string) => string | undefined
17
30
  /** The locales this project declares. */
18
31
  export const locales = ['ar', 'en'] as const
19
32
 
33
+ /** The app reads either message-file format during the upgrade release. */
34
+ export const copyFile = (file: Record<string, unknown>): Record<string, Record<string, string>> =>
35
+ Object.fromEntries(Object.entries(flattenMessageFile({ file, locales })).map(([key, value]) =>
36
+ [key, typeof value === 'string' ? Object.fromEntries(locales.map((locale) => [locale, value])) : value ?? {}]))
37
+
20
38
  /** One of the locales this project declares. */
21
39
  export type Locale = (typeof locales)[number]
22
40
 
@@ -42,20 +60,17 @@ export const localeOf = (asked: string | null | undefined): Locale =>
42
60
  /** Which way a document in one locale runs. */
43
61
  export const dirOf = (locale: Locale): 'rtl' | 'ltr' => (locale === 'ar' ? 'rtl' : 'ltr')
44
62
 
45
- /**
46
- * Every scope this app's `t` can answer from. Each entry carries a string per declared
47
- * locale: a key left unanswered here shows an Arabic viewer the English fallback.
48
- */
49
- export const scopes: Record<string, Record<string, Record<string, string>>> = {
63
+ /** The copy every organization mode starts from; `scopes` below lays the mode's words over it. */
64
+ const ownScopes: Record<string, Record<string, Record<string, string>>> = {
65
+ // add-on: courses
50
66
  courses: {
51
- ...courses,
67
+ ...copyFile(courses),
52
68
  'delete.confirm': { en: 'Delete this course', ar: 'احذف هذه الدورة' },
53
69
  dangerZoneWarning: { en: 'These actions change this course for everyone.', ar: 'هذه الإجراءات تغير هذه الدورة للجميع.' },
54
70
  none: { en: 'No courses yet.', ar: 'لا توجد دورات بعد.' },
55
71
  noneOnOffer: { en: 'No courses on offer yet.', ar: 'لا توجد دورات معروضة بعد.' },
56
72
  'operations.archive': { en: 'Archive', ar: 'أرشفة' },
57
73
  'operations.archive.warning': { en: 'This archives {name}.', ar: 'يؤرشف هذا الإجراء {name}.' },
58
- 'operations.archive.portalReason': { en: 'Archived from the portal by {name}', ar: 'أُرشفت من البوابة بواسطة {name}' },
59
74
  'operations.findWithSessions': { en: 'View with its sessions', ar: 'عرض مع جلساتها' },
60
75
  new: { en: 'New course', ar: 'دورة جديدة' },
61
76
  'archived.true': { en: 'Archived', ar: 'مؤرشف' },
@@ -68,7 +83,12 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
68
83
  en: 'An archive reason is at most 200 characters.',
69
84
  ar: 'سبب الأرشفة 200 حرف على الأكثر.',
70
85
  },
86
+ 'operations.unarchive': { en: 'Unarchive', ar: 'إلغاء الأرشفة' },
87
+ 'operations.unarchive.reason.required': { en: 'A reason to bring it back is required.', ar: 'سبب إلغاء الأرشفة مطلوب.' },
88
+ 'operations.unarchive.reason.maxLength': { en: 'A reason to bring it back is at most 200 characters.', ar: 'سبب إلغاء الأرشفة 200 حرف على الأكثر.' },
89
+ 'operations.unpublish': { en: 'Unpublish', ar: 'إلغاء النشر' },
71
90
  },
91
+ // end add-on: courses
72
92
  admin: {
73
93
  nav: { en: 'Admin', ar: 'الإدارة' },
74
94
  yes: { en: 'Yes', ar: 'نعم' },
@@ -91,6 +111,17 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
91
111
  stopImpersonating: { en: 'Stop impersonating', ar: 'إيقاف انتحال الصفة' },
92
112
  cancel: { en: 'Cancel', ar: 'إلغاء' },
93
113
  close: { en: 'Close', ar: 'إغلاق' },
114
+ 'done.created': { en: '{kind} created', ar: 'تم إنشاء {kind}' },
115
+ 'done.saved': { en: '{kind} saved', ar: 'تم حفظ {kind}' },
116
+ 'done.published': { en: '{kind} published', ar: 'تم نشر {kind}' },
117
+ 'done.draftSaved': { en: 'Draft saved', ar: 'تم حفظ المسودة' },
118
+ 'done.deleted': { en: '{kind} deleted', ar: 'تم حذف {kind}' },
119
+ 'done.archive': { en: '{kind} archived', ar: 'تمت أرشفة {kind}' },
120
+ 'done.unarchive': { en: '{kind} unarchived', ar: 'تم إلغاء أرشفة {kind}' },
121
+ 'done.unpublish': { en: '{kind} unpublished', ar: 'تم إلغاء نشر {kind}' },
122
+ 'done.restoreVersion': { en: 'Version restored', ar: 'استُعيدت النسخة' },
123
+ 'done.ran': { en: '{operation}: done', ar: '{operation}: تم' },
124
+ requestFailed: { en: 'Something went wrong, so nothing was saved. Try again.', ar: 'حدث خطأ، فلم يُحفظ شيء. حاول مرة أخرى.' },
94
125
  deleteWarning: { en: 'This cannot be undone.', ar: 'لا يمكن التراجع عن هذا.' },
95
126
  deleteConfirm: { en: 'Delete for good', ar: 'احذف نهائيا' },
96
127
  deleteFailed: { en: 'Something went wrong, so nothing was deleted. Try again.', ar: 'حدث خطأ، فلم يُحذف شيء. حاول مرة أخرى.' },
@@ -192,6 +223,9 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
192
223
  noMatches: { en: 'No rows match this filter.', ar: 'لا توجد صفوف تطابق هذا المرشح.' },
193
224
  refused: { en: 'That list could not be read.', ar: 'تعذرت قراءة هذه القائمة.' },
194
225
  cannotSee: { en: 'You can\'t see this', ar: 'لا يمكنك رؤية هذا' },
226
+ // A held id with no row: one a read of its kayan answered without, and one the viewer may not read.
227
+ deleted: { en: 'Deleted', ar: 'محذوف' },
228
+ notAvailable: { en: 'Not available', ar: 'غير متاح' },
195
229
  clearFilters: { en: 'Clear filters', ar: 'مسح المرشحات' },
196
230
  selectAll: { en: 'Select every row on this page', ar: 'حدد كل صف في هذه الصفحة' },
197
231
  selectRow: { en: 'Select this row', ar: 'حدد هذا الصف' },
@@ -230,6 +264,13 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
230
264
  permissions: { en: 'Permissions', ar: 'الصلاحيات' },
231
265
  record: { en: 'Record', ar: 'السجل' },
232
266
  invalidFields: { en: 'Fields needing attention: {count}', ar: 'حقول تحتاج إلى مراجعة: {count}' },
267
+ errorSummary: { en: 'Check these fields:', ar: 'راجع هذه الحقول:' },
268
+ addNew: { en: 'Add new {kind}', ar: 'إضافة {kind}' },
269
+ backToList: { en: 'Back to list', ar: 'العودة إلى القائمة' },
270
+ reload: { en: 'Reload', ar: 'إعادة التحميل' },
271
+ hours: { en: 'Hours', ar: 'الساعات' },
272
+ minutes: { en: 'Minutes', ar: 'الدقائق' },
273
+ period: { en: 'AM or PM', ar: 'صباحا أو مساء' },
233
274
  organizations: { en: 'Organizations', ar: 'المؤسسات' },
234
275
  organization: { en: 'Organization', ar: 'مؤسسة' },
235
276
  noOrganization: { en: 'No organization', ar: 'لا توجد مؤسسة' },
@@ -254,15 +295,116 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
254
295
  welcome: { en: 'Welcome, {name}', ar: 'مرحبا، {name}' },
255
296
  about: { en: 'What you may open here, and how many of each you may read.', ar: 'ما يمكنك فتحه هنا، وعدد ما يمكنك قراءته من كل منها.' },
256
297
  open: { en: 'Open the list', ar: 'افتح القائمة' },
298
+ // add-on: courses
299
+ title: { en: 'Portal', ar: 'البوابة' },
300
+ navigation: { en: 'Portal', ar: 'البوابة' },
301
+ myEnrollments: { en: 'My enrollments', ar: 'تسجيلاتي' },
302
+ 'greeting.morning': { en: 'Good morning, {name}', ar: 'صباح الخير يا {name}' },
303
+ 'greeting.afternoon': { en: 'Good afternoon, {name}', ar: 'مساء الخير يا {name}' },
304
+ 'greeting.evening': { en: 'Good evening, {name}', ar: 'مساء الخير يا {name}' },
305
+ yourNext: { en: 'Your next session', ar: 'جلستك القادمة' },
306
+ startsIn: { en: 'Starts {when}', ar: 'تبدأ {when}' },
307
+ viewCourse: { en: 'View course', ar: 'عرض الدورة' },
308
+ canJoin: { en: 'Courses you can join', ar: 'دورات يمكنك الانضمام إليها' },
309
+ seeAll: { en: 'See all courses', ar: 'كل الدورات' },
310
+ noneToJoin: { en: 'No course has a session coming up just now.', ar: 'لا توجد دورة لها جلسة قادمة الآن.' },
311
+ search: { en: 'Search courses', ar: 'ابحث في الدورات' },
312
+ byTag: { en: 'Filter by tag', ar: 'التصفية حسب الوسم' },
313
+ allTags: { en: 'All', ar: 'الكل' },
314
+ noMatch: { en: 'No course matches.', ar: 'لا توجد دورة مطابقة.' },
315
+ next: { en: 'Next session', ar: 'الجلسة القادمة' },
316
+ noSession: { en: 'No session scheduled yet', ar: 'لا توجد جلسة مجدولة بعد' },
317
+ free: { en: 'Free', ar: 'مجانا' },
318
+ perSeat: { en: 'per seat', ar: 'لكل مقعد' },
319
+ back: { en: 'Back to courses', ar: 'العودة إلى الدورات' },
320
+ aboutCourse: { en: 'About this course', ar: 'عن هذه الدورة' },
321
+ choose: { en: 'Choose a session', ar: 'اختر جلسة' },
322
+ full: { en: 'Full', ar: 'مكتملة' },
323
+ seatsLeft_zero: { en: '{count} seats left', ar: 'لم يبق أي مقعد' },
324
+ seatsLeft_one: { en: '{count} seat left', ar: 'بقي مقعد واحد' },
325
+ seatsLeft_two: { en: '{count} seats left', ar: 'بقي مقعدان' },
326
+ seatsLeft_few: { en: '{count} seats left', ar: 'بقيت {count} مقاعد' },
327
+ seatsLeft_many: { en: '{count} seats left', ar: 'بقي {count} مقعدا' },
328
+ seatsLeft_other: { en: '{count} seats left', ar: 'بقي {count} مقعد' },
329
+ total: { en: 'Total', ar: 'الإجمالي' },
330
+ pick: { en: 'Pick a session', ar: 'اختر جلسة' },
331
+ enrollIn: { en: 'Enroll on {when}', ar: 'سجّلني في {when}' },
332
+ enrolled: { en: 'You are enrolled.', ar: 'تم تسجيلك.' },
333
+ enrollFailed: { en: 'The seat was not taken. Please try again.', ar: 'لم يُحجز المقعد. حاول مرة أخرى.' },
334
+ holding: { en: 'You hold a seat on {when}.', ar: 'لك مقعد في {when}.' },
335
+ askSchool: { en: 'Ask your school to enroll you.', ar: 'اطلب من مدرستك تسجيلك.' },
336
+ upcoming: { en: 'Upcoming', ar: 'القادمة' },
337
+ past: { en: 'Past', ar: 'السابقة' },
338
+ noneUpcoming: { en: 'No session to come.', ar: 'لا توجد جلسة قادمة.' },
339
+ nonePast: { en: 'No past session.', ar: 'لا توجد جلسة سابقة.' },
340
+ course: { en: 'Course', ar: 'الدورة' },
341
+ place: { en: 'Venue and format', ar: 'المكان والصيغة' },
342
+ leave: { en: 'Cancel enrollment', ar: 'إلغاء التسجيل' },
343
+ leaveTitle: { en: 'Cancel this enrollment?', ar: 'إلغاء هذا التسجيل؟' },
344
+ leaveBody: { en: 'Your seat goes back to the session, and someone else may take it.', ar: 'يعود مقعدك إلى الجلسة، وقد يأخذه غيرك.' },
345
+ keep: { en: 'Keep it', ar: 'أبقِه' },
346
+ left: { en: 'Your enrollment is cancelled.', ar: 'أُلغي تسجيلك.' },
347
+ leaveFailed: { en: 'The enrollment was not cancelled. Please try again.', ar: 'لم يُلغَ التسجيل. حاول مرة أخرى.' },
348
+ failed: { en: 'This could not be read just now.', ar: 'تعذرت قراءة هذا الآن.' },
349
+ retry: { en: 'Try again', ar: 'حاول مرة أخرى' },
350
+ // end add-on: courses
351
+ // add-on: courses, settings
352
+ closed: { en: 'Closed for maintenance', ar: 'مغلقة للصيانة' },
353
+ closedBody: { en: 'The school is working on the portal. Please come back soon.', ar: 'تعمل المدرسة على البوابة. عد قريبا من فضلك.' },
354
+ contact: { en: 'Questions? Write to', ar: 'لديك سؤال؟ راسل' },
355
+ // end add-on: courses, settings
257
356
  },
258
357
  auth: {
259
358
  signIn: { en: 'Sign in', ar: 'تسجيل الدخول' },
260
- signInDescription: { en: 'Sign in to reach the dashboard.', ar: 'سجل الدخول للوصول إلى لوحة التحكم.' },
261
- signInPlatformDescription: { en: 'Sign in to reach the platform.', ar: 'سجل الدخول للوصول إلى المنصة.' },
359
+ signInDescription: { en: 'Welcome back. Sign in with your email and password.', ar: 'مرحبا بعودتك. سجّل الدخول ببريدك الإلكتروني وكلمة المرور.' },
360
+ signInBackofficeDescription: { en: 'Sign in to reach the backoffice.', ar: 'سجل الدخول للوصول إلى لوحة الإدارة الخلفية.' },
262
361
  signInPending: { en: 'Signing in…', ar: 'جارٍ تسجيل الدخول…' },
362
+ createFirstUser: { en: 'Create the first administrator', ar: 'إنشاء أول مسؤول' },
363
+ createFirstUserDescription: {
364
+ en: 'No one administers this backoffice yet. The account made here becomes its first administrator.',
365
+ ar: 'لا أحد يدير لوحة الإدارة الخلفية هذه بعد. الحساب الذي يُنشأ هنا يصبح أول مسؤول عنها.',
366
+ },
367
+ createFirstUserSubmit: { en: 'Create the administrator', ar: 'إنشاء المسؤول' },
368
+ createFirstUserPending: { en: 'Creating…', ar: 'جارٍ الإنشاء…' },
369
+ createFirstUserFailed: { en: 'That administrator could not be created.', ar: 'تعذر إنشاء هذا المسؤول.' },
370
+ setupCode: { en: 'Setup code', ar: 'رمز الإعداد' },
371
+ setupCodeDescription: { en: 'The SETUP_CODE this server was started with.', ar: 'قيمة SETUP_CODE التي شُغّل بها هذا الخادم.' },
372
+ setupCodeWrong: { en: 'That is not the setup code.', ar: 'هذا ليس رمز الإعداد.' },
373
+ setupCodeUnset: {
374
+ en: 'The first administrator is made only with a setup code, and this server has none it can use: SETUP_CODE is unset or shorter than 32 characters. Set SETUP_CODE where the backoffice app runs to a random value of 32 characters or more, restart it, and open this page again.',
375
+ ar: 'لا يُنشأ أول مسؤول إلا برمز إعداد، وليس لدى هذا الخادم رمز يمكنه استعماله: SETUP_CODE غير مضبوط أو أقصر من 32 حرفا. اضبط SETUP_CODE حيث يعمل تطبيق لوحة الإدارة الخلفية على قيمة عشوائية من 32 حرفا أو أكثر، ثم أعد تشغيله وافتح هذه الصفحة مرة أخرى.',
376
+ },
377
+ firstRegisterClosed: {
378
+ en: 'This backoffice has its administrator, so this page is closed. Sign in instead.',
379
+ ar: 'للوحة الإدارة الخلفية هذه مسؤولها، لذا أُغلقت هذه الصفحة. سجل الدخول بدلا من ذلك.',
380
+ },
381
+ firstRun: { en: 'Set up this app', ar: 'إعداد هذا التطبيق' },
382
+ firstRunDescription: {
383
+ en: 'Nobody owns this app yet. The account made here becomes its owner and first administrator.',
384
+ ar: 'لا أحد يملك هذا التطبيق بعد. الحساب الذي يُنشأ هنا يصبح مالكه وأول مسؤول عنه.',
385
+ },
386
+ firstRunSubmit: { en: 'Set up the app', ar: 'إعداد التطبيق' },
387
+ firstRunCodeUnset: {
388
+ en: 'This app is set up only with a setup code, and this server has none it can use: SETUP_CODE is unset or shorter than 32 characters. Set SETUP_CODE where the app runs to a random value of 32 characters or more, restart it, and open this page again.',
389
+ ar: 'لا يُعدّ هذا التطبيق إلا برمز إعداد، وليس لدى هذا الخادم رمز يمكنه استعماله: SETUP_CODE غير مضبوط أو أقصر من 32 حرفا. اضبط SETUP_CODE حيث يعمل التطبيق على قيمة عشوائية من 32 حرفا أو أكثر، ثم أعد تشغيله وافتح هذه الصفحة مرة أخرى.',
390
+ },
391
+ firstRunPending: { en: 'Setting up…', ar: 'جارٍ الإعداد…' },
392
+ firstRunFailed: { en: 'That owner could not be made.', ar: 'تعذر إنشاء هذا المالك.' },
393
+ firstRunClosed: { en: 'This app has its owner, so this page is closed. Sign in instead.', ar: 'لهذا التطبيق مالكه، لذا أُغلقت هذه الصفحة. سجل الدخول بدلا من ذلك.' },
394
+ setupPending: { en: 'This app is not set up yet. Its administrator sets it up first.', ar: 'لم يُعدّ هذا التطبيق بعد. يُعدّه مسؤوله أولا.' },
395
+ inviteOnly: {
396
+ en: 'Accounts here are made by invitation. Open the link of the invitation sent to you, or sign in.',
397
+ ar: 'تُنشأ الحسابات هنا بدعوة. افتح رابط الدعوة المرسلة إليك، أو سجل الدخول.',
398
+ },
399
+ outsideDomains: { en: 'This email address is not at a domain that may sign up here.', ar: 'هذا البريد الإلكتروني ليس من نطاق يُسمح له بإنشاء حساب هنا.' },
400
+ verifySent: { en: 'Check your email', ar: 'تحقق من بريدك الإلكتروني' },
401
+ verifySentDescription: {
402
+ en: 'We sent a link to {email}. Open it to confirm your address and choose your password; it signs you in. It lasts an hour.',
403
+ ar: 'أرسلنا رابطا إلى {email}. افتحه لتأكيد عنوانك واختيار كلمة المرور، فتدخل به مباشرة. وهو صالح ساعة.',
404
+ },
263
405
  failed: { en: 'That sign-in did not work.', ar: 'لم ينجح تسجيل الدخول.' },
264
406
  signUp: { en: 'Sign up', ar: 'إنشاء حساب' },
265
- signUpDescription: { en: 'Make an account and its organization.', ar: 'أنشئ حسابا ومؤسسته.' },
407
+ signUpDescription: { en: 'Make an account.', ar: 'أنشئ حسابا.' },
266
408
  signUpPending: { en: 'Signing up…', ar: 'جارٍ إنشاء الحساب…' },
267
409
  signUpFailed: { en: 'That sign-up did not work.', ar: 'لم ينجح إنشاء الحساب.' },
268
410
  organizationFailed: { en: 'That organization could not be created.', ar: 'تعذر إنشاء هذه المؤسسة.' },
@@ -296,6 +438,10 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
296
438
  },
297
439
  backToSignIn: { en: 'Back to login', ar: 'العودة إلى تسجيل الدخول' },
298
440
  resetPassword: { en: 'Reset password', ar: 'إعادة تعيين كلمة المرور' },
441
+ setPassword: { en: 'Choose your password', ar: 'اختر كلمة المرور' },
442
+ organizationNameIn: { en: 'Its name in {language}, if it has one', ar: 'اسمها بـ{language}، إن كان لها اسم بها' },
443
+ setPasswordSubmit: { en: 'Confirm and set password', ar: 'أكّد واحفظ كلمة المرور' },
444
+ passwordChosen: { en: 'Your address is confirmed and your password is set. Sign in with it.', ar: 'تأكّد عنوانك وحُفظت كلمة المرور. سجّل الدخول بها.' },
299
445
  newPassword: { en: 'New password', ar: 'كلمة مرور جديدة' },
300
446
  passwordReset: { en: 'Password reset successfully.', ar: 'تمت إعادة تعيين كلمة المرور بنجاح.' },
301
447
  tokenInvalid: { en: 'Token is either invalid or has expired.', ar: 'الرمز غير صالح أو انتهت صلاحيته.' },
@@ -308,7 +454,10 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
308
454
  },
309
455
  tooManyRequests: { en: 'Too many attempts. Wait a moment and try again.', ar: 'محاولات كثيرة جدا. انتظر قليلا ثم حاول مرة أخرى.' },
310
456
  invalidCredentials: { en: 'The email or password provided is incorrect.', ar: 'البريد الإلكتروني أو كلمة المرور غير صحيحة.' },
311
- emailNotVerified: { en: 'This email address is not verified yet.', ar: 'لم يوثق هذا البريد الإلكتروني بعد.' },
457
+ emailNotVerified: {
458
+ en: 'This email address is not confirmed yet. Open the link we sent to it, or ask for a password reset to get a new one.',
459
+ ar: 'لم يُؤكَّد هذا البريد الإلكتروني بعد. افتح الرابط الذي أرسلناه إليه، أو اطلب إعادة تعيين كلمة المرور لتحصل على رابط جديد.',
460
+ },
312
461
  banned: { en: 'This account is banned, so it cannot sign in.', ar: 'هذا الحساب محظور، لذا لا يمكنه تسجيل الدخول.' },
313
462
  userExists: { en: 'An account with this email already exists. Use another email.', ar: 'يوجد حساب بهذا البريد الإلكتروني. استخدم بريدا آخر.' },
314
463
  requestFailed: { en: 'That request did not work.', ar: 'لم ينجح هذا الطلب.' },
@@ -342,8 +491,8 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
342
491
  ar: 'أنشئ حسابا بالبريد الإلكتروني الذي أُرسلت إليه الدعوة.',
343
492
  },
344
493
  haveAccount: { en: 'Already have an account?', ar: 'لديك حساب بالفعل؟' },
345
- unauthorized: { en: 'Not for your role', ar: 'ليست لدورك' },
346
- unauthorizedDescription: { en: 'Your role does not open that screen. These are the screens it opens:', ar: 'دورك لا يفتح تلك الشاشة. هذه هي الشاشات التي يفتحها:' },
494
+ unauthorized: { en: 'Choose where to go', ar: 'اختر وجهتك' },
495
+ unauthorizedDescription: { en: 'This page isn\'t part of your account. Where would you like to go?', ar: 'هذه الصفحة ليست ضمن حسابك. إلى أين تريد الذهاب؟' },
347
496
  unauthorizedPage: { en: 'Your role does not open that page: {page}.', ar: 'دورك لا يفتح تلك الصفحة: {page}.' },
348
497
  noAccess: { en: 'Your account has no access yet', ar: 'لا يملك حسابك أي وصول بعد' },
349
498
  noAccessDescription: {
@@ -352,34 +501,27 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
352
501
  },
353
502
  switchTo: { en: 'Switch to {organization}', ar: 'التبديل إلى {organization}' },
354
503
  accountStatus: { en: 'See your account status', ar: 'اطّلع على حالة حسابك' },
355
- noOrganization: {
356
- en: '{email} belongs to no organization yet. Ask an administrator to invite you.',
357
- ar: '{email} لا ينتمي إلى أي مؤسسة بعد. اطلب من أحد المسؤولين أن يدعوك.',
358
- },
504
+ noOrganization: { en: '{email} belongs to no organization yet.', ar: '{email} لا ينتمي إلى أي مؤسسة بعد.' },
505
+ yourInvitations: { en: 'Your invitations', ar: 'دعواتك' },
506
+ invitationDecline: { en: 'Decline', ar: 'رفض' },
507
+ createOrganization: { en: 'Create an organization', ar: 'أنشئ مؤسسة' },
508
+ createOrganizationSubmit: { en: 'Create', ar: 'إنشاء' },
509
+ workspaceOf: { en: '{name}\'s workspace', ar: 'مساحة عمل {name}' },
510
+ askToBeInvited: { en: 'Ask an administrator to invite {email}.', ar: 'اطلب من أحد المسؤولين أن يدعو {email}.' },
359
511
  },
360
512
  marketing: {
361
513
  brand: { en: 'Asas', ar: 'أساس' },
362
514
  getStarted: { en: 'Get started', ar: 'ابدأ الآن' },
363
515
  openScreen: { en: 'Open {screen}', ar: 'افتح {screen}' },
364
- heroTitle: { en: 'Run courses and enrollments without the busywork', ar: 'أدر الدورات والتسجيلات دون العمل الروتيني' },
516
+ heroTitle: { en: 'Run your organization without the busywork', ar: 'أدر مؤسستك دون العمل الروتيني' },
365
517
  heroSubtitle: {
366
- en: 'Asas is a dashboard and a portal for organizations that teach: manage courses, see who enrolled, and keep every role to what it should see.',
367
- ar: 'أساس لوحة تحكم وبوابة للمؤسسات التي تُعلّم: أدر الدورات، اطّلع على من سجل، واحصر كل دور بما ينبغي أن يراه.',
518
+ en: 'Asas is a dashboard and a portal for organizations: keep your records in one place, and keep every role to what it should see.',
519
+ ar: 'أساس لوحة تحكم وبوابة للمؤسسات: احفظ سجلاتك في مكان واحد، واحصر كل دور بما ينبغي أن يراه.',
368
520
  },
369
- featuresTitle: { en: 'Everything a training organization needs', ar: 'كل ما تحتاجه مؤسسة تدريبية' },
521
+ featuresTitle: { en: 'Everything an organization needs', ar: 'كل ما تحتاجه مؤسسة' },
370
522
  featuresSubtitle: {
371
- en: 'One system for the whole lifecycle, from listing a course to seeing who is enrolled in it.',
372
- ar: 'نظام واحد لدورة الحياة كاملة، من نشر دورة إلى معرفة من سجل فيها.',
373
- },
374
- featureCoursesTitle: { en: 'Courses and sessions', ar: 'الدورات والجلسات' },
375
- featureCoursesDescription: {
376
- en: 'Create courses, set their fee and schedule, and open or archive them as they run.',
377
- ar: 'أنشئ الدورات، حدد رسومها وجدولها، وافتحها أو أرشفها أثناء انعقادها.',
378
- },
379
- featureEnrollmentsTitle: { en: 'Enrollments', ar: 'التسجيلات' },
380
- featureEnrollmentsDescription: {
381
- en: 'See who enrolled in each course and when, with the day-by-day count on the dashboard.',
382
- ar: 'اطلع على من سجل في كل دورة ومتى، مع العدد اليومي في لوحة التحكم.',
523
+ en: 'One system for your records, from the first entry to who may see it.',
524
+ ar: 'نظام واحد لسجلاتك، من أول إدخال إلى من يحق له رؤيته.',
383
525
  },
384
526
  featureRolesTitle: { en: 'Roles and permissions', ar: 'الأدوار والصلاحيات' },
385
527
  featureRolesDescription: {
@@ -399,23 +541,23 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
399
541
  howTitle: { en: 'How it works', ar: 'كيف يعمل' },
400
542
  howStep1Title: { en: 'Set up your organization', ar: 'أنشئ مؤسستك' },
401
543
  howStep1Description: { en: 'Sign up and create your organization in one step.', ar: 'أنشئ حسابك ومؤسستك في خطوة واحدة.' },
402
- howStep2Title: { en: 'Add courses and roles', ar: 'أضف الدورات والأدوار' },
544
+ howStep2Title: { en: 'Add your records and roles', ar: 'أضف سجلاتك وأدوارك' },
403
545
  howStep2Description: {
404
- en: 'List your courses and decide what each role in your organization may do.',
405
- ar: 'أدرج دوراتك وحدد ما يحق لكل دور في مؤسستك القيام به.',
546
+ en: 'List what your organization keeps and decide what each role may do.',
547
+ ar: 'أدرج ما تحفظه مؤسستك وحدد ما يحق لكل دور القيام به.',
406
548
  },
407
549
  howStep3Title: { en: 'Open the portal to your customers', ar: 'افتح البوابة لعملائك' },
408
550
  howStep3Description: {
409
- en: 'Customers sign in to their own portal to see the courses they may enroll in.',
410
- ar: 'يسجل العملاء الدخول إلى بوابتهم الخاصة لرؤية الدورات التي يمكنهم التسجيل فيها.',
551
+ en: 'Customers sign in to their own portal to see what is theirs.',
552
+ ar: 'يسجل العملاء الدخول إلى بوابتهم الخاصة لرؤية ما يخصهم.',
411
553
  },
412
- ctaTitle: { en: 'Start running your courses on Asas', ar: 'ابدأ بإدارة دوراتك على أساس' },
413
- ctaSubtitle: { en: 'Create your organization and add your first course in minutes.', ar: 'أنشئ مؤسستك وأضف أول دورة لك خلال دقائق.' },
414
- footer: { en: 'Asas — courses and enrollments for training organizations.', ar: 'أساس — الدورات والتسجيلات للمؤسسات التدريبية.' },
554
+ ctaTitle: { en: 'Start running your organization on Asas', ar: 'ابدأ بإدارة مؤسستك على أساس' },
555
+ ctaSubtitle: { en: 'Create your organization and add your first record in minutes.', ar: 'أنشئ مؤسستك وأضف أول سجل لك خلال دقائق.' },
556
+ footer: { en: 'Asas — one place for an organization\'s records.', ar: 'أساس — مكان واحد لسجلات المؤسسة.' },
415
557
  },
416
558
  dashboard: {
417
559
  dashboard: { en: 'Dashboard', ar: 'لوحة التحكم' },
418
- openPlatform: { en: 'Open the platform', ar: 'فتح المنصة' },
560
+ openBackoffice: { en: 'Open the backoffice', ar: 'فتح لوحة الإدارة الخلفية' },
419
561
  members: { en: 'Members', ar: 'الأعضاء' },
420
562
  membersAbout: { en: 'Who works in this organization, and the role each holds.', ar: 'من يعمل في هذه المؤسسة، والدور الذي يشغله كل منهم.' },
421
563
  membersRoles: { en: 'Roles', ar: 'الأدوار' },
@@ -424,6 +566,20 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
424
566
  membersJoined: { en: 'Joined', ar: 'تاريخ الانضمام' },
425
567
  membersNone: { en: 'No one works in this organization yet.', ar: 'لا يعمل أحد في هذه المؤسسة بعد.' },
426
568
  invite: { en: 'Invite', ar: 'دعوة' },
569
+ addPerson: { en: 'Add person', ar: 'إضافة شخص' },
570
+ addPersonTitle: { en: 'Add a person', ar: 'إضافة شخص' },
571
+ addPersonAbout: {
572
+ en: 'They get an account here and a link by email to choose their password. Someone who already has an account is sent an invitation instead.',
573
+ ar: 'يحصل على حساب هنا ورابط بالبريد لاختيار كلمة مروره. ومن لديه حساب بالفعل تُرسل إليه دعوة بدلا من ذلك.',
574
+ },
575
+ addPersonRole: { en: 'Role', ar: 'الدور' },
576
+ addPersonNoRole: { en: 'No role yet', ar: 'بلا دور بعد' },
577
+ addPersonSubmit: { en: 'Add', ar: 'إضافة' },
578
+ addPersonLink: {
579
+ en: 'No mailer is configured. Hand this link to them alone: it chooses their password and lasts 48 hours.',
580
+ ar: 'لم يُضبط أي مرسل بريد. سلّم هذا الرابط له وحده: به يختار كلمة مروره، وهو صالح 48 ساعة.',
581
+ },
582
+ addPersonRefused: { en: 'The server refused to add that person.', ar: 'رفض الخادم إضافة هذا الشخص.' },
427
583
  inviteTitle: { en: 'Invite someone', ar: 'دعوة شخص' },
428
584
  inviteAbout: {
429
585
  en: 'They join this organization as a member once they accept. Give them roles here after that.',
@@ -431,14 +587,25 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
431
587
  },
432
588
  inviteSend: { en: 'Send invitation', ar: 'إرسال الدعوة' },
433
589
  inviteRefused: { en: 'The server refused that invitation.', ar: 'رفض الخادم هذه الدعوة.' },
590
+ inviteSent: { en: 'Invitation sent to {email}', ar: 'أُرسلت الدعوة إلى {email}' },
591
+ inviteCancelled: { en: 'Invitation cancelled', ar: 'أُلغيت الدعوة' },
592
+ rolesSaved: { en: 'Roles saved for {name}', ar: 'حُفظت أدوار {name}' },
593
+ memberRemoved: { en: '{name} removed', ar: 'أُزيل {name}' },
594
+ personAdded: { en: '{name} added. A link to choose a password was sent to {email}.', ar: 'أُضيف {name}. أُرسل إلى {email} رابط لاختيار كلمة المرور.' },
595
+ personAddedLink: { en: '{name} added. Hand them the link below.', ar: 'أُضيف {name}. سلّمه الرابط أدناه.' },
434
596
  inviteAlreadyMember: { en: 'This person is already a member of this organization.', ar: 'هذا الشخص عضو في هذه المؤسسة بالفعل.' },
435
597
  inviteAlreadyInvited: { en: 'This person already has an invitation that is not yet accepted.', ar: 'لدى هذا الشخص دعوة لم تُقبل بعد.' },
436
598
  inviteNotAllowed: { en: 'You may not invite people to this organization.', ar: 'لا يحق لك دعوة أشخاص إلى هذه المؤسسة.' },
437
599
  inviteNoMailer: {
438
- en: 'No mailer is configured, so no email is sent. In development the link is written to the server log, and each pending invitation has a link to copy.',
439
- ar: 'لم يُضبط أي مرسل بريد، لذا لا تُرسل أي رسالة. في بيئة التطوير يُكتب الرابط في سجل الخادم، ولكل دعوة لم تُقبل بعد رابط يمكن نسخه.',
600
+ en: 'No mailer is configured, so no email is sent. In development the link is written to the server log.',
601
+ ar: 'لم يُضبط أي مرسل بريد، لذا لا تُرسل أي رسالة. في بيئة التطوير يُكتب الرابط في سجل الخادم.',
440
602
  },
441
603
  inviteCopy: { en: 'Copy link', ar: 'نسخ الرابط' },
604
+ link: { en: 'Link', ar: 'الرابط' },
605
+ inviteLinkOwn: {
606
+ en: 'A copied link lets whoever holds it join as the invited address: hand it to that person alone.',
607
+ ar: 'الرابط المنسوخ يتيح لمن يحمله الانضمام بالعنوان المدعو: سلّمه لذلك الشخص وحده.',
608
+ },
442
609
  inviteCopied: { en: 'Link copied', ar: 'تم نسخ الرابط' },
443
610
  inviteCancel: { en: 'Cancel invitation', ar: 'إلغاء الدعوة' },
444
611
  inviteCancelRefused: { en: 'The server refused to cancel that invitation.', ar: 'رفض الخادم إلغاء هذه الدعوة.' },
@@ -454,13 +621,31 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
454
621
  removeLastOwner: { en: 'The organization\'s only owner cannot be removed.', ar: 'لا يمكن إزالة المالك الوحيد للمؤسسة.' },
455
622
  removeNotAllowed: { en: 'You may not remove members of this organization.', ar: 'لا يحق لك إزالة أعضاء هذه المؤسسة.' },
456
623
  rolesHolderUnnamed: { en: 'A member', ar: 'أحد الأعضاء' },
457
- invitesPending: { en: 'Invitations not yet accepted', ar: 'دعوات لم تُقبل بعد' },
624
+ invitationsTab: { en: 'Invitations ({count})', ar: 'الدعوات ({count})' },
625
+ invitesNone: { en: 'No invitation is waiting to be accepted.', ar: 'لا توجد دعوة تنتظر القبول.' },
626
+ inviteByLink: { en: 'Invite by link', ar: 'الدعوة برابط' },
627
+ inviteSentBy: { en: 'Sent by', ar: 'أرسلها' },
628
+ inviteExpired: { en: 'Expired', ar: 'منتهية' },
629
+ inviteResend: { en: 'Resend', ar: 'إعادة الإرسال' },
630
+ inviteResent: { en: 'Invitation sent again to {email}', ar: 'أُعيد إرسال الدعوة إلى {email}' },
631
+ membersRoleFilter: { en: 'Role', ar: 'الدور' },
632
+ membersEveryRole: { en: 'Every role', ar: 'كل الأدوار' },
633
+ membersNotGrantable: {
634
+ en: 'It grants keys you do not hold, so only someone who holds them gives or takes it.',
635
+ ar: 'يمنح صلاحيات لا تملكها، لذا لا يمنحه أو يسحبه إلا من يملكها.',
636
+ },
637
+ passwordLink: { en: 'Send a new password link', ar: 'إرسال رابط جديد لكلمة المرور' },
638
+ passwordLinkSent: { en: 'A link to choose a password was sent to {email}', ar: 'أُرسل إلى {email} رابط لاختيار كلمة المرور' },
639
+ passwordLinkRefused: { en: 'The server refused to send that link.', ar: 'رفض الخادم إرسال هذا الرابط.' },
640
+ passwordLinkHanded: {
641
+ en: 'No mailer is configured. Hand this link to {name} alone: it chooses their password and lasts 48 hours.',
642
+ ar: 'لم يُضبط أي مرسل بريد. سلّم هذا الرابط إلى {name} وحده: به يختار كلمة مروره، وهو صالح 48 ساعة.',
643
+ },
644
+ removeTitle: { en: 'Remove {name}?', ar: 'إزالة {name}؟' },
458
645
  inviteExpires: { en: 'Expires', ar: 'تنتهي في' },
459
- membersActions: { en: 'Actions', ar: 'الإجراءات' },
460
646
  membersEdit: { en: 'Edit roles', ar: 'تعديل الأدوار' },
461
647
  membersFixed: { en: 'Fixed roles', ar: 'الأدوار الثابتة' },
462
648
  membersCustom: { en: 'Custom roles', ar: 'الأدوار المخصصة' },
463
- membersPermissions: { en: '{count} permissions', ar: 'الصلاحيات: {count}' },
464
649
  membersNoRoles: { en: 'There is no role you may read to assign.', ar: 'لا يوجد دور يمكنك قراءته لإسناده.' },
465
650
  membersRefused: { en: 'The server refused that change.', ar: 'رفض الخادم هذا التغيير.' },
466
651
  everyPermission: { en: 'Every permission', ar: 'كل الصلاحيات' },
@@ -472,6 +657,7 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
472
657
  rolesKind: { en: 'Kind', ar: 'النوع' },
473
658
  rolesFixed: { en: 'Fixed', ar: 'ثابت' },
474
659
  rolesCustom: { en: 'Custom', ar: 'مخصص' },
660
+ rolesOpen: { en: 'Open {role}', ar: 'فتح {role}' },
475
661
  rolesFixedAbout: {
476
662
  en: 'This role ships with the project. What it holds is code, so it cannot be edited or deleted here.',
477
663
  ar: 'يأتي هذا الدور مع المشروع. ما يمنحه مكتوب في الشيفرة، لذا لا يمكن تعديله أو حذفه هنا.',
@@ -484,6 +670,8 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
484
670
  },
485
671
  permissionsScreens: { en: 'Screens', ar: 'الشاشات' },
486
672
  permissionsFields: { en: 'Fields', ar: 'الحقول' },
673
+ permissionsCount: { en: '{count} permissions', ar: 'الصلاحيات: {count}' },
674
+ permissionsCountOne: { en: '1 permission', ar: 'صلاحية واحدة' },
487
675
  permissionsOther: { en: 'Other', ar: 'أخرى' },
488
676
  permissionsRequired: { en: 'Permissions, required', ar: 'الصلاحيات، مطلوبة' },
489
677
  membersOwnRole: {
@@ -535,20 +723,20 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
535
723
  screens: {
536
724
  dashboard: { en: 'Dashboard', ar: 'لوحة التحكم' },
537
725
  portal: { en: 'Portal', ar: 'البوابة' },
538
- platform: { en: 'Platform', ar: 'المنصة' },
539
- },
540
- platform: {
541
- title: { en: 'Platform', ar: 'المنصة' },
542
- brand: { en: 'Asas platform', ar: 'منصة أساس' },
543
- about: { en: 'Every organization and every person on the platform, for its staff.', ar: 'كل مؤسسة وكل شخص على المنصة، لفريقها.' },
544
- role: { en: 'Platform role', ar: 'دور المنصة' },
545
- noRole: { en: 'No platform role', ar: 'بلا دور في المنصة' },
726
+ backoffice: { en: 'Backoffice', ar: 'لوحة الإدارة الخلفية' },
727
+ },
728
+ backoffice: {
729
+ title: { en: 'Backoffice', ar: 'لوحة الإدارة الخلفية' },
730
+ brand: { en: 'Asas backoffice', ar: 'لوحة الإدارة الخلفية لأساس' },
731
+ about: { en: 'Every organization and every person, for the backoffice staff.', ar: 'كل مؤسسة وكل شخص، لفريق لوحة الإدارة الخلفية.' },
732
+ role: { en: 'Backoffice role', ar: 'دور في لوحة الإدارة الخلفية' },
733
+ noRole: { en: 'No backoffice role', ar: 'بلا دور في لوحة الإدارة الخلفية' },
546
734
  setRole: { en: 'Set role', ar: 'تعيين الدور' },
547
735
  ban: { en: 'Ban', ar: 'حظر' },
548
736
  unban: { en: 'Unban', ar: 'رفع الحظر' },
549
737
  impersonate: { en: 'Impersonate', ar: 'انتحال الصفة' },
550
- confirmRole: { en: 'Give {name} the platform role "{role}"?', ar: 'منح {name} دور المنصة "{role}"؟' },
551
- pickRole: { en: 'Pick another platform role for {name}.', ar: 'اختر دورًا آخر في المنصة لـ {name}.' },
738
+ confirmRole: { en: 'Give {name} the backoffice role "{role}"?', ar: 'منح {name} الدور "{role}" في لوحة الإدارة الخلفية؟' },
739
+ pickRole: { en: 'Pick another backoffice role for {name}.', ar: 'اختر دورًا آخر في لوحة الإدارة الخلفية لـ {name}.' },
552
740
  confirmBan: {
553
741
  en: 'Ban {name}? They are signed out of both apps and cannot sign in until the ban ends.',
554
742
  ar: 'حظر {name}؟ سيُسجَّل خروجه من التطبيقين ولن يتمكن من تسجيل الدخول حتى ينتهي الحظر.',
@@ -558,7 +746,7 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
558
746
  en: 'Impersonate {name}? You act as them in the tenant app until you stop.',
559
747
  ar: 'انتحال صفة {name}؟ ستتصرف باسمه في تطبيق المؤسسة حتى تتوقف.',
560
748
  },
561
- roleSet: { en: '{name} now holds the platform role "{role}".', ar: 'أصبح دور {name} في المنصة "{role}".' },
749
+ roleSet: { en: '{name} now holds the backoffice role "{role}".', ar: 'أصبح دور {name} في لوحة الإدارة الخلفية "{role}".' },
562
750
  banned: { en: '{name} is banned.', ar: 'تم حظر {name}.' },
563
751
  unbanned: { en: 'The ban on {name} is lifted.', ar: 'تم رفع الحظر عن {name}.' },
564
752
  renamed: { en: 'The organization is now named {name}.', ar: 'أصبح اسم المؤسسة {name}.' },
@@ -571,6 +759,7 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
571
759
  banWeek: { en: 'Seven days', ar: 'سبعة أيام' },
572
760
  banMonth: { en: 'Thirty days', ar: 'ثلاثون يومًا' },
573
761
  rename: { en: 'Rename', ar: 'إعادة التسمية' },
762
+ nameIn: { en: 'Name in {language}, if it has one', ar: 'الاسم بـ{language}، إن كان لها اسم بها' },
574
763
  slugPattern: {
575
764
  en: 'A slug is lowercase letters and digits joined by single dashes, such as admin-org.',
576
765
  ar: 'المعرف النصي أحرف إنجليزية صغيرة وأرقام تفصل بينها شرطات مفردة، مثل admin-org.',
@@ -580,7 +769,7 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
580
769
  ar: 'يحل الاسم والمعرّف الجديدان محل القديمين لكل من في هذه المؤسسة.',
581
770
  },
582
771
  members: { en: 'Members', ar: 'الأعضاء' },
583
- refused: { en: 'The platform refused that action.', ar: 'رفضت المنصة هذا الإجراء.' },
772
+ refused: { en: 'The backoffice refused that action.', ar: 'رفضت لوحة الإدارة الخلفية هذا الإجراء.' },
584
773
  'membership.owner': { en: 'Owner', ar: 'المالك' },
585
774
  'membership.admin': { en: 'Admin', ar: 'مشرف' },
586
775
  'membership.member': { en: 'Member', ar: 'عضو' },
@@ -594,7 +783,7 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
594
783
  email: { en: 'Email', ar: 'البريد الإلكتروني' },
595
784
  emailVerified: { en: 'Email verified', ar: 'البريد موثق' },
596
785
  image: { en: 'Image', ar: 'الصورة' },
597
- role: { en: 'Platform role', ar: 'دور المنصة' },
786
+ role: { en: 'Backoffice role', ar: 'دور في لوحة الإدارة الخلفية' },
598
787
  banned: { en: 'Banned', ar: 'محظور' },
599
788
  banReason: { en: 'Ban reason', ar: 'سبب الحظر' },
600
789
  banExpires: { en: 'Ban ends', ar: 'ينتهي الحظر' },
@@ -602,7 +791,7 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
602
791
  updatedAt: { en: 'Updated at', ar: 'تاريخ التعديل' },
603
792
  },
604
793
  organization: {
605
- ...organization,
794
+ ...copyFile(organization),
606
795
  singular: { en: 'Organization', ar: 'مؤسسة' },
607
796
  plural: { en: 'Organizations', ar: 'المؤسسات' },
608
797
  none: { en: 'No organizations yet.', ar: 'لا توجد مؤسسات بعد.' },
@@ -624,14 +813,18 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
624
813
  role: { en: 'Membership role', ar: 'دور العضوية' },
625
814
  createdAt: { en: 'Created at', ar: 'تاريخ الإنشاء' },
626
815
  updatedAt: { en: 'Updated at', ar: 'تاريخ التعديل' },
816
+ lastSeenAt: { en: 'Last seen', ar: 'آخر ظهور' },
627
817
  },
818
+ // add-on: settings
628
819
  settings: {
629
- ...settings,
820
+ ...copyFile(settings),
630
821
  singular: { en: 'Settings', ar: 'الإعدادات' },
631
822
  plural: { en: 'Settings', ar: 'الإعدادات' },
632
823
  },
824
+ // end add-on: settings
825
+ // add-on: roles
633
826
  roles: {
634
- ...roles,
827
+ ...copyFile(roles),
635
828
  singular: { en: 'Role', ar: 'دور' },
636
829
  plural: { en: 'Roles', ar: 'الأدوار' },
637
830
  'delete.confirm': { en: 'Delete this role', ar: 'احذف هذا الدور' },
@@ -646,17 +839,27 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
646
839
  ar: 'المعرف النصي للدور، الذي يسميه كل إسناد له، مثل course-editor. يقرأ الناس الدور بتسميته أدناه.',
647
840
  },
648
841
  },
842
+ // end add-on: roles
843
+ // add-on: courses
649
844
  sessions: {
650
- ...sessions,
845
+ ...copyFile(sessions),
651
846
  singular: { en: 'Session', ar: 'جلسة' },
652
847
  plural: { en: 'Sessions', ar: 'الجلسات' },
653
848
  'operations.archiveMany': { en: 'Archive many', ar: 'أرشفة عدة جلسات' },
849
+ 'operations.archive': { en: 'Archive', ar: 'أرشفة' },
850
+ 'operations.archive.reason.required': { en: 'An archive reason is required.', ar: 'سبب الأرشفة مطلوب.' },
851
+ 'operations.archive.reason.maxLength': { en: 'An archive reason is at most 200 characters.', ar: 'سبب الأرشفة 200 حرف على الأكثر.' },
852
+ 'operations.unarchive': { en: 'Unarchive', ar: 'إلغاء الأرشفة' },
853
+ 'operations.unarchive.reason.required': { en: 'A reason to bring it back is required.', ar: 'سبب إلغاء الأرشفة مطلوب.' },
854
+ 'operations.unarchive.reason.maxLength': { en: 'A reason to bring it back is at most 200 characters.', ar: 'سبب إلغاء الأرشفة 200 حرف على الأكثر.' },
654
855
  new: { en: 'New session', ar: 'جلسة جديدة' },
655
856
  none: { en: 'No sessions yet.', ar: 'لا توجد جلسات بعد.' },
656
857
  dangerZoneWarning: { en: 'These actions change this session for everyone.', ar: 'هذه الإجراءات تغير هذه الجلسة للجميع.' },
657
858
  },
859
+ // end add-on: courses
860
+ // add-on: courses
658
861
  enrollments: {
659
- ...enrollments,
862
+ ...copyFile(enrollments),
660
863
  singular: { en: 'Enrollment', ar: 'تسجيل' },
661
864
  plural: { en: 'Enrollments', ar: 'التسجيلات' },
662
865
  'operations.others': { en: 'Everyone\'s enrollments', ar: 'تسجيلات الجميع' },
@@ -664,14 +867,16 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
664
867
  none: { en: 'No enrollments yet.', ar: 'لا توجد تسجيلات بعد.' },
665
868
  dangerZoneWarning: { en: 'These actions change this enrollment for everyone.', ar: 'هذه الإجراءات تغير هذا التسجيل للجميع.' },
666
869
  },
870
+ // end add-on: courses
667
871
  tags: {
668
- ...tags,
872
+ ...copyFile(tags),
669
873
  new: { en: 'New tag', ar: 'وسم جديد' },
670
874
  none: { en: 'No tags yet.', ar: 'لا توجد وسوم بعد.' },
671
875
  dangerZoneWarning: { en: 'These actions change this tag for everyone.', ar: 'هذه الإجراءات تغير هذا الوسم للجميع.' },
672
876
  },
877
+ // add-on: media
673
878
  media: {
674
- ...media,
879
+ ...copyFile(media),
675
880
  new: { en: 'New image', ar: 'صورة جديدة' },
676
881
  none: { en: 'No images yet.', ar: 'لا توجد صور بعد.' },
677
882
  newAbout: { en: 'Choose an image and describe it. It is uploaded before it is saved.', ar: 'اختر صورة وصِفها. تُرفع قبل أن تُحفظ.' },
@@ -680,21 +885,30 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
680
885
  replaceAbout: { en: 'Replace file uploads a new image and opens it. This one stays until you delete it.', ar: 'يرفع استبدال الملف صورة جديدة ويفتحها. تبقى هذه الصورة حتى تحذفها.' },
681
886
  dangerZoneWarning: { en: 'These actions change this image for everyone.', ar: 'هذه الإجراءات تغير هذه الصورة للجميع.' },
682
887
  },
683
- localUsers: { ...localUsers, none: { en: 'No local accounts yet.', ar: 'لا توجد حسابات محلية بعد.' } },
684
- auditLog,
888
+ // end add-on: media
889
+ localUsers: { ...copyFile(localUsers), none: { en: 'No local accounts yet.', ar: 'لا توجد حسابات محلية بعد.' } },
890
+ // add-on: audit-log
891
+ auditLog: copyFile(auditLog),
892
+ backofficeLog: { ...copyFile(backofficeLog), none: { en: 'No staff action yet.', ar: 'لا يوجد إجراء للطاقم بعد.' } },
893
+ // end add-on: audit-log
894
+ // add-on: roles
685
895
  memberRoles: {
686
- ...memberRoles,
896
+ ...copyFile(memberRoles),
687
897
  singular: { en: 'Member role', ar: 'دور عضو' },
688
898
  plural: { en: 'Member roles', ar: 'أدوار الأعضاء' },
689
899
  none: { en: 'No member roles yet.', ar: 'لا توجد أدوار أعضاء بعد.' },
690
900
  },
901
+ // end add-on: roles
691
902
  version: {
692
903
  saveDraft: { en: 'Save draft', ar: 'حفظ المسودة' },
904
+ noReadableChange: { en: 'No field you may read changed.', ar: 'لم يتغير حقل يمكنك قراءته.' },
905
+ publish: { en: 'Publish', ar: 'نشر' },
693
906
  publishChanges: { en: 'Publish changes', ar: 'نشر التغييرات' },
694
907
  status: { en: 'Status', ar: 'الحالة' },
695
908
  draft: { en: 'Draft', ar: 'مسودة' },
696
909
  published: { en: 'Published', ar: 'منشور' },
697
910
  changed: { en: 'Changed', ar: 'تغييرات غير منشورة' },
911
+ archived: { en: 'Archived', ar: 'مؤرشف' },
698
912
  saving: { en: 'Saving…', ar: 'جارٍ الحفظ…' },
699
913
  saved: { en: 'Saved', ar: 'تم الحفظ' },
700
914
  updatedAgo: { en: 'Updated {time}', ar: 'تم التحديث {time}' },
@@ -782,6 +996,8 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
782
996
  ar: 'تسجيل الدخول المحلي متوقف على هذا الخادم: لم يُضبط LOCAL_AUTH_SECRET. اضبطه بسر من 32 حرفا على الأقل وأعد تشغيل الخادم.',
783
997
  },
784
998
  'not_found.row': { en: 'That row is no longer there.', ar: 'لم يعد هذا السجل موجودا.' },
999
+ 'conflict.retry': { en: 'Another write interrupted this transaction. Try again.', ar: 'تعارضت هذه المعاملة مع عملية أخرى. حاول مرة أخرى.' },
1000
+ 'conflict.target': { en: 'The authorized record cannot change.', ar: 'لا يمكن تغيير السجل المصرح به.' },
785
1001
  'conflict.unique': { en: 'Another row already holds that value.', ar: 'يحمل سجل آخر هذه القيمة بالفعل.' },
786
1002
  'conflict.stale': { en: 'This was changed by someone else since you opened it. Reload it and make your change again.', ar: 'غيّر شخص آخر هذا منذ فتحته. أعد تحميله ثم أعد إجراء تغييرك.' },
787
1003
  'invalid.reference': { en: 'That record is not one you may choose.', ar: 'هذا السجل ليس مما يمكنك اختياره.' },
@@ -796,6 +1012,15 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
796
1012
  'forbidden.owner_role': { en: 'The owner of the organization always holds the administrator role, so it is not taken from them.', ar: 'مالك المؤسسة يشغل دور المدير دائما، لذا لا يُسحب منه.' },
797
1013
  'invalid.member_role': { en: 'A role is named exactly, with no spaces around it.', ar: 'يُسمّى الدور بدقة، دون مسافات حوله.' },
798
1014
  'conflict.last_manager': { en: 'This would leave the organization with nobody who may manage roles. Give another member such a role first.', ar: 'سيترك هذا المؤسسة دون أي عضو يملك إدارة الأدوار. امنح عضوا آخر دورا كهذا أولا.' },
1015
+ 'invalid.archived_session': { en: 'This session is archived, or its course is, so it takes no new seat. Pick an open session.', ar: 'هذه الحصة مؤرشفة، أو دورتها مؤرشفة، فلا تقبل مقعدا جديدا. اختر حصة مفتوحة.' },
1016
+ // add-on: courses
1017
+ 'invalid.enrollment_closed': { en: 'Students of this course do not enroll themselves. Ask your school to enroll you.', ar: 'لا يسجّل طلاب هذه الدورة أنفسهم. اطلب من مدرستك تسجيلك.' },
1018
+ 'invalid.session_started': { en: 'This session has started, so its seats no longer change.', ar: 'بدأت هذه الجلسة، فلم تعد مقاعدها تتغير.' },
1019
+ 'conflict.session_full': { en: 'Every seat of this session is taken. Pick another session.', ar: 'كل مقاعد هذه الجلسة محجوزة. اختر جلسة أخرى.' },
1020
+ // end add-on: courses
1021
+ // add-on: courses, settings
1022
+ 'invalid.portal_closed': { en: 'The portal is closed for maintenance, so no enrollment changes from it just now.', ar: 'البوابة مغلقة للصيانة، فلا يتغير منها أي تسجيل الآن.' },
1023
+ // end add-on: courses, settings
799
1024
  'invalid.draft_sessions': { en: 'A course saved as a draft makes no sessions. Publish the course, then add its sessions.', ar: 'الدورة المحفوظة كمسودة لا تُنشئ جلسات. انشر الدورة أولًا، ثم أضف جلساتها.' },
800
1025
  'invalid.upload_request': { en: 'The file has no name, or no size, that can be uploaded.', ar: 'ليس للملف اسم أو حجم يمكن رفعه.' },
801
1026
  'invalid.upload_type': { en: 'Files of this type are not taken here.', ar: 'لا تُقبل هنا ملفات من هذا النوع.' },
@@ -803,18 +1028,110 @@ export const scopes: Record<string, Record<string, Record<string, string>>> = {
803
1028
  'invalid.upload_reference': { en: 'The upload has expired or is not this one. Upload the file again.', ar: 'انتهت صلاحية الرفع أو أنه ليس لهذا الملف. ارفع الملف مرة أخرى.' },
804
1029
  'invalid.upload_verify': { en: 'The file is not what it said it was, so it was not kept. Upload another.', ar: 'الملف ليس كما ادّعى، لذا لم يُحفظ. ارفع ملفا آخر.' },
805
1030
  'not_found.file': { en: 'That file is not here.', ar: 'هذا الملف غير موجود.' },
806
- 'conflict.referenced': {
807
- en: 'Something still uses this, so it is not deleted. An image stays while a course shows it. A session stays while an enrollment records a seat in it, and a person stays while one records their seat, a withdrawn one included.',
808
- ar: 'ما زال شيء يستخدم هذا، لذا لا يُحذف. تبقى الصورة ما دامت دورة تعرضها. وتبقى الحصة ما دام تسجيل يحفظ مقعدا فيها، ويبقى الشخص ما دام تسجيل يحفظ مقعده، ولو كان قد انسحب.',
809
- },
1031
+ 'conflict.referenced': { en: 'Something still uses this, so it is not deleted.', ar: 'ما زال شيء يستخدم هذا، لذا لا يُحذف.' },
810
1032
  'conflict.member_holds_roles': {
811
1033
  en: 'This member still holds a role in this organization. Take each role from them first, then remove them.',
812
1034
  ar: 'ما زال هذا العضو يشغل دورا في هذه المؤسسة. اسحب منه كل دور أولا، ثم أزله.',
813
1035
  },
814
1036
  'conflict.referencedBy': { en: '{kind} still use this, so it is not deleted.', ar: 'ما زالت {kind} تستخدم هذا، لذا لا يُحذف.' },
1037
+ 'conflict.usedBy': { en: 'Used by {count} {kind}: {titles}.', ar: 'تستخدمه {kind} ({count}): {titles}.' },
1038
+ 'conflict.usedByUnnamed': { en: 'Used by {count} {kind}.', ar: 'تستخدمه {kind} ({count}).' },
1039
+ 'conflict.usedMore': { en: '{count} more', ar: '{count} أخرى' },
1040
+ 'conflict.usedHint': { en: 'Take it out of them first.', ar: 'أزله منها أولا.' },
1041
+ 'conflict.sole_owner': {
1042
+ en: 'This person is the only owner of an organization, so they are not deleted. Make another member its owner first.',
1043
+ ar: 'هذا الشخص هو المالك الوحيد لمؤسسة، لذا لا يُحذف. اجعل عضوا آخر مالكا لها أولا.',
1044
+ },
815
1045
  },
816
1046
  }
817
1047
 
1048
+ type Wording = Record<string, Record<string, { en: string; ar: string }>>
1049
+
1050
+ /** With one organization, nobody founds one on signing up: an owner sets the app up once. */
1051
+ const one: Wording = {
1052
+ auth: {
1053
+ noOrganization: { en: '{email} has no access here yet.', ar: 'لا يملك {email} وصولا هنا بعد.' },
1054
+ },
1055
+ marketing: {
1056
+ howStep1Title: { en: 'Set it up once', ar: 'أعدّه مرة واحدة' },
1057
+ howStep1Description: { en: 'Its owner sets the app up, then invites the people who use it.', ar: 'يُعدّ مالكه التطبيق، ثم يدعو من يستعملونه.' },
1058
+ ctaSubtitle: { en: 'Sign in and add your first record in minutes.', ar: 'سجل الدخول وأضف أول سجل لك خلال دقائق.' },
1059
+ },
1060
+ }
1061
+
1062
+ /** With the one organization never shown, it is the app itself, so no page names an organization. */
1063
+ const unnamed: Wording = {
1064
+ auth: {
1065
+ invitationJoin: { en: 'You are invited to join this app.', ar: 'أنت مدعو للانضمام إلى هذا التطبيق.' },
1066
+ invitationInviterGone: {
1067
+ en: 'Whoever sent this invitation no longer uses this app, so it cannot be accepted. Ask someone else to invite you.',
1068
+ ar: 'من أرسل هذه الدعوة لم يعد يستعمل هذا التطبيق، لذا لا يمكن قبولها. اطلب من شخص آخر أن يدعوك.',
1069
+ },
1070
+ noAccessDescription: { en: '{email} holds no role that opens a screen here. Ask an administrator to give you one.', ar: '{email} لا يملك دورا يفتح أي شاشة هنا. اطلب من أحد المسؤولين أن يمنحك دورا.' },
1071
+ },
1072
+ admin: {
1073
+ noColumns: { en: 'Your role can\'t see any column of this list. Ask an administrator for access.', ar: 'لا يستطيع دورك رؤية أي عمود في هذه القائمة. اطلب الوصول من أحد المسؤولين.' },
1074
+ },
1075
+ marketing: {
1076
+ heroTitle: { en: 'Run your work without the busywork', ar: 'أدر عملك دون العمل الروتيني' },
1077
+ heroSubtitle: {
1078
+ en: 'Asas is a dashboard and a portal: keep your records in one place, and keep every role to what it should see.',
1079
+ ar: 'أساس لوحة تحكم وبوابة: احفظ سجلاتك في مكان واحد، واحصر كل دور بما ينبغي أن يراه.',
1080
+ },
1081
+ featuresTitle: { en: 'Everything you need', ar: 'كل ما تحتاجه' },
1082
+ featureRolesDescription: {
1083
+ en: 'Every screen, field and action follows what a role may read or write.',
1084
+ ar: 'تتبع كل شاشة وحقل وإجراء ما يحق للدور قراءته أو تعديله.',
1085
+ },
1086
+ howStep2Description: { en: 'List what you keep and decide what each role may do.', ar: 'أدرج ما تحفظه وحدد ما يحق لكل دور القيام به.' },
1087
+ ctaTitle: { en: 'Start on Asas', ar: 'ابدأ على أساس' },
1088
+ footer: { en: 'Asas — one place for your records.', ar: 'أساس — مكان واحد لسجلاتك.' },
1089
+ },
1090
+ dashboard: {
1091
+ members: { en: 'Users', ar: 'المستخدمون' },
1092
+ membersAbout: { en: 'Who uses this app, and the role each holds.', ar: 'من يستعمل هذا التطبيق، والدور الذي يشغله كل منهم.' },
1093
+ membersNone: { en: 'No one uses this app yet.', ar: 'لا يستعمل أحد هذا التطبيق بعد.' },
1094
+ inviteAbout: { en: 'They join as a user once they accept. Give them roles here after that.', ar: 'ينضم مستخدمًا بعد أن يقبل الدعوة. امنحه الأدوار هنا بعد ذلك.' },
1095
+ inviteAlreadyMember: { en: 'This person already uses this app.', ar: 'هذا الشخص يستعمل هذا التطبيق بالفعل.' },
1096
+ inviteNotAllowed: { en: 'You may not invite people.', ar: 'لا يحق لك دعوة أشخاص.' },
1097
+ removeMember: { en: 'Remove from the app', ar: 'إزالة من التطبيق' },
1098
+ removeAbout: {
1099
+ en: 'They lose access to this app and every role they hold in it. Inviting them again does not give the roles back.',
1100
+ ar: 'يفقد الوصول إلى هذا التطبيق وكل الأدوار التي يشغلها فيه. دعوته مرة أخرى لا تعيد إليه أدواره.',
1101
+ },
1102
+ removeLastOwner: { en: 'The app\'s only owner cannot be removed.', ar: 'لا يمكن إزالة المالك الوحيد للتطبيق.' },
1103
+ removeNotAllowed: { en: 'You may not remove users.', ar: 'لا يحق لك إزالة مستخدمين.' },
1104
+ rolesAbout: { en: 'Fixed roles ship with the project and cannot be changed here. Custom roles are this app\'s own.', ar: 'الأدوار الثابتة تأتي مع المشروع ولا يمكن تغييرها هنا. الأدوار المخصصة خاصة بهذا التطبيق.' },
1105
+ membersOwnerRole: { en: 'The owner of the app always holds this role.', ar: 'مالك التطبيق يشغل هذا الدور دائما.' },
1106
+ dashboardAbout: { en: 'What this app holds, and how many have enrolled lately.', ar: 'ما يضمه هذا التطبيق، وعدد من سجلوا مؤخرا.' },
1107
+ dashboardAboutCatalogue: { en: 'What this app holds.', ar: 'ما يضمه هذا التطبيق.' },
1108
+ },
1109
+ error: {
1110
+ 'invalid.tenant_reference': { en: 'That record is not one of this app\'s.', ar: 'هذا السجل ليس من سجلات هذا التطبيق.' },
1111
+ 'conflict.last_manager': { en: 'This would leave nobody who may manage roles. Give another user such a role first.', ar: 'سيترك هذا التطبيق دون أي مستخدم يملك إدارة الأدوار. امنح مستخدما آخر دورا كهذا أولا.' },
1112
+ 'forbidden.owner_role': { en: 'The owner of the app always holds the administrator role, so it is not taken from them.', ar: 'مالك التطبيق يشغل دور المدير دائما، لذا لا يُسحب منه.' },
1113
+ 'invalid.role': { en: 'That role is not one of this app\'s.', ar: 'هذا الدور ليس من أدوار هذا التطبيق.' },
1114
+ 'conflict.role_assigned': { en: 'An assignment still names that role, so no role takes the name until it is removed.', ar: 'ما زال إسناد يسمي هذا الدور، لذا لا يأخذ أي دور هذا الاسم حتى يُزال.' },
1115
+ 'conflict.member_holds_roles': { en: 'This user still holds a role. Take each role from them first, then remove them.', ar: 'ما زال هذا المستخدم يشغل دورا. اسحب منه كل دور أولا، ثم أزله.' },
1116
+ 'conflict.sole_owner': { en: 'This person is the app\'s only owner, so they are not deleted. Make another user an owner first.', ar: 'هذا الشخص هو المالك الوحيد للتطبيق، لذا لا يُحذف. اجعل مستخدما آخر مالكا أولا.' },
1117
+ },
1118
+ }
1119
+
1120
+ /** The words the organization mode changes, laid over the base copy key by key. */
1121
+ export const wordingFor = (mode: { organizations: 'one' | 'many'; showOrganization: boolean }): Wording =>
1122
+ mode.organizations === 'many' ? {} : mode.showOrganization ? one : Object.fromEntries([...new Set([...Object.keys(one), ...Object.keys(unnamed)])].map((scope) => [scope, { ...one[scope], ...unnamed[scope] }]))
1123
+
1124
+ /** The base copy with `wording` laid over it, so a key the mode changes reads the mode's words. */
1125
+ export const withWording = (base: typeof ownScopes, wording: Wording) =>
1126
+ Object.fromEntries(Object.entries(base).map(([scope, keys]) => [scope, { ...keys, ...wording[scope] }])) as typeof ownScopes
1127
+
1128
+ /**
1129
+ * Every scope this app's `t` can answer from, in this project's organization mode. Each entry
1130
+ * carries a string per declared locale: a key left unanswered here shows an Arabic viewer the
1131
+ * English fallback.
1132
+ */
1133
+ export const scopes = withWording(ownScopes, wordingFor({ organizations, showOrganization }))
1134
+
818
1135
  // A `Map`, so a code named for a key of `Object.prototype` answers nothing. A taken organization
819
1136
  // name has no entry: names may repeat, and sign-up makes the slug unique itself.
820
1137
  const refusalKeys = new Map([
@@ -834,6 +1151,13 @@ const refusalKeys = new Map([
834
1151
  ['YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER', 'dashboard.removeNotAllowed'],
835
1152
  ['RESET_PASSWORD_DISABLED', 'auth.resetDisabled'],
836
1153
  ['INVALID_TOKEN', 'auth.tokenInvalid'],
1154
+ ['SETUP_CODE_WRONG', 'auth.setupCodeWrong'],
1155
+ ['SETUP_CODE_UNSET', 'auth.setupCodeUnset'],
1156
+ ['FIRST_REGISTER_CLOSED', 'auth.firstRegisterClosed'],
1157
+ ['FIRST_RUN_CLOSED', 'auth.firstRunClosed'],
1158
+ ['FIRST_RUN_PENDING', 'auth.setupPending'],
1159
+ ['SIGN_UP_INVITE_ONLY', 'auth.inviteOnly'],
1160
+ ['SIGN_UP_DOMAIN', 'auth.outsideDomains'],
837
1161
  ])
838
1162
  /** The key of this app's own sentence for a provider refusal, or nothing. The provider's `message` is English on every page and is never shown. */
839
1163
  export const refusalKey = (status: number, code: unknown): string | undefined =>
@@ -859,9 +1183,22 @@ export const documentHead = ({ locale, app }: { locale: Locale; app?: string },
859
1183
  return { meta: [{ title: name && own ? `${name} · ${own}` : name || own }] }
860
1184
  }
861
1185
 
1186
+ /**
1187
+ * Sets the cookie the request middleware reads and redraws the page in `to` where it stands, as Payload's language
1188
+ * switch refreshes its router: nothing typed into a form is lost. The root route reads the locale off the document.
1189
+ */
1190
+ export const showLocale = async ({ to, router }: { to: string; router: { invalidate(): Promise<unknown> } }) => {
1191
+ const locale = localeOf(to)
1192
+ document.cookie = localeCookieValue(locale)
1193
+ document.documentElement.lang = locale
1194
+ document.documentElement.dir = dirOf(locale)
1195
+ await router.invalidate()
1196
+ }
1197
+
862
1198
  /**
863
1199
  * Sets the cookie the request middleware reads, then reloads so the server draws the document
864
- * in it. The unsaved-changes guard is dropped because the shell already asked in its own dialog.
1200
+ * in it: a screen's form is seeded again from whatever row the host hands, so the shell asks before it
1201
+ * drops a change nobody saved, and its own unsaved-changes guard is dropped because the shell already asked.
865
1202
  */
866
1203
  export const switchLocale = ({ to }: { to: string }): void => {
867
1204
  window.onbeforeunload = null