-
Notifications
You must be signed in to change notification settings - Fork 30
web-next: Add user invitation system #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Implements invitation functionality to allow existing users to invite new users via email. Includes GraphQL schema updates, resolver implementation, and internationalization support across all supported locales. - Add `invite` mutation to GraphQL schema - Implement invitation validation and email sending logic - Add invitation-related error types and result unions - Update locale files with invitation-related translations - Add invitation quota tracking for accounts
Implements a locale selection dropdown component using Solid and Relay. The component displays available locales with native language names and integrates with the existing i18n infrastructure.
Implements the invitation functionality in the settings area, allowing users to send invitations to new users. Includes form validation, locale selection, and integration with the GraphQL invite mutation. - Add invite tab to settings navigation - Create invite settings page with email and message form - Add locale selection for invitation recipients - Update i18n negotiateLocale to accept readonly arrays - Display invitation quotas and validation errors
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds a GraphQL invitation feature (types, mutation, validation, token creation, email templating/sending with 48h expiry), locale discovery, locale-aware email templates and picker, frontend invite settings UI and route, i18n typing refinements, and small UI spacing/label fixes. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Web as "Web UI"
participant GQL as "GraphQL API"
participant KV as "KV / DB"
participant I18N as "Locale Templates (FS)"
participant Mail as "Mailer"
User->>Web: Open Invite page
Web->>GQL: query availableLocales
GQL->>I18N: read locales/*.json
I18N-->>GQL: locales list
GQL-->>Web: [Intl.Locale]
User->>Web: submit invite(email, locale, message, verifyUrl)
Web->>GQL: mutation invite(...)
GQL->>KV: verify inviter auth & quota
alt validation fails
GQL-->>Web: InviteValidationErrors
else
GQL->>KV: atomic decrement leftInvitations
GQL->>KV: create signup token (48h)
GQL->>I18N: load & select template (negotiateLocale)
GQL->>Mail: send email (expanded template)
alt mail fails
GQL->>KV: credit back leftInvitations
GQL-->>Web: InviteValidationErrors (INVITER_EMAIL_SEND_FAILED)
else
GQL-->>Web: Invitation
end
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @dahlia, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request introduces a new core feature that empowers existing users to expand the platform's community by inviting new members. It provides a complete end-to-end solution for user invitations, encompassing backend API logic, email delivery with internationalization, and a dedicated user interface for managing the invitation process. The primary goal is to facilitate organic growth and streamline the onboarding of new users through a controlled invitation mechanism.
Highlights
- User Invitation System: Implemented a comprehensive user invitation system allowing existing users to invite new users via email.
- GraphQL API Enhancements: Introduced a new
invite
GraphQL mutation with robust validation, including checks for email uniqueness and invitation quotas. - Internationalized Email Invitations: Enabled personalized invitation emails with support for multiple locales (English, Japanese, Korean, Simplified Chinese, Traditional Chinese), ensuring recipients receive invitations in their preferred language.
- Web UI for Invitations: Added a new dedicated settings page at
/settings/invite
on the web UI, providing a user-friendly interface for sending invitations with real-time validation and feedback on remaining invitations. - Invitation Quota Management: Integrated functionality to track and enforce invitation limits per user, decrementing available invitations upon successful sends.
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command>
or @gemini-code-assist <command>
. Below is a summary of the supported commands.
Feature | Command | Description |
---|---|---|
Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/
folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request introduces a comprehensive user invitation system, including a new GraphQL mutation, email notifications with localization, and a user-friendly web interface. The implementation is well-structured and covers various aspects from backend logic to frontend integration. I've identified a few areas for improvement, mainly concerning error handling and performance, to enhance the robustness and efficiency of the new system.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (14)
graphql/locales/ko.json (2)
9-9
: Fix minor Korean spelling: “메세지” → “메시지”.Standard spelling improves UX.
Apply this diff:
- "emailContentWithMessage": "{{inviter}} 님이 Hackers' Pub에 초대합니다! 다음은 {{inviterName}} 님의 메세지입니다:\n\n{{message}}\n\n초대장을 받으려면 아래 링크를 클릭하세요:\n\n{{verifyUrl}}\n\n이 링크는 {{expiration}} 후에 만료됩니다.\n" + "emailContentWithMessage": "{{inviter}} 님이 Hackers' Pub에 초대합니다! 다음은 {{inviterName}} 님의 메시지입니다:\n\n{{message}}\n\n초대장을 받으려면 아래 링크를 클릭하세요:\n\n{{verifyUrl}}\n\n이 링크는 {{expiration}} 후에 만료됩니다.\n"
8-8
: Optional phrasing tweak: “초대장을 받으려면” → “초대를 수락하려면”.Closer to the English intent (“accept the invitation”).
Apply this diff:
- "emailContent": "{{inviter}} 님이 Hackers' Pub에 초대합니다!\n\nHackers' Pub은 소프트웨어 프로그래머들이 지식과 경험을 서로 나누는 곳입니다. 또한 ActivityPub을 지원하는 소셜 네트워크이기도 하며, 연합우주(fediverse)에서 즐겨찾는 소프트웨어 프로그래머들을 팔로하고 최신 콘텐츠를 받아 볼 수 있습니다.\n\n초대장을 받으려면 아래 링크를 클릭하세요:\n\n{{verifyUrl}}\n\n이 링크는 {{expiration}} 후에 만료됩니다.\n", + "emailContent": "{{inviter}} 님이 Hackers' Pub에 초대합니다!\n\nHackers' Pub은 소프트웨어 프로그래머들이 지식과 경험을 서로 나누는 곳입니다. 또한 ActivityPub을 지원하는 소셜 네트워크이기도 하며, 연합우주(fediverse)에서 즐겨찾는 소프트웨어 프로그래머들을 팔로하고 최신 콘텐츠를 받아 볼 수 있습니다.\n\n초대를 수락하려면 아래 링크를 클릭하세요:\n\n{{verifyUrl}}\n\n이 링크는 {{expiration}} 후에 만료됩니다.\n",models/i18n.ts (2)
189-206
: Precompute maximize() for available locales to avoid repeated work.maximize() is called inside three loops; precomputing reduces allocations and CPU in hot paths.
Apply this diff:
- const availables = availableLocales.map((l) => - typeof l === "string" ? new Intl.Locale(l) : l - ); + const availables = availableLocales.map((l) => + typeof l === "string" ? new Intl.Locale(l) : l + ); + const pre = availables.map((raw) => ({ raw, max: raw.maximize() })); @@ - // First try exact match - for (const available of availables) { - const availableMaximized = available.maximize(); - if (wantedMaximized.baseName === availableMaximized.baseName) { - return available; - } - } + // First try exact match + for (const a of pre) { + if (wantedMaximized.baseName === a.max.baseName) { + return a.raw; + } + } @@ - for (const available of availables) { - const availableMaximized = available.maximize(); - if ( - wantedMaximized.language === availableMaximized.language && - wantedMaximized.script === availableMaximized.script - ) { - return available; - } - } + for (const a of pre) { + if ( + wantedMaximized.language === a.max.language && + wantedMaximized.script === a.max.script + ) { + return a.raw; + } + } @@ - for (const available of availables) { - const availableMaximized = available.maximize(); - if (wantedMaximized.language === availableMaximized.language) { - return available; - } - } + for (const a of pre) { + if (wantedMaximized.language === a.max.language) { + return a.raw; + } + }
189-191
: Minor naming nit: prefer “availableLocalesNormalized” over “availables”.Improves readability.
Apply this diff:
- const availables = availableLocales.map((l) => + const availableLocalesNormalized = availableLocales.map((l) => typeof l === "string" ? new Intl.Locale(l) : l ); - const pre = availables.map((raw) => ({ raw, max: raw.maximize() })); + const pre = availableLocalesNormalized.map((raw) => ({ raw, max: raw.maximize() }));web-next/src/routes/(root)/[handle]/settings/preferences.tsx (1)
199-206
: Label “for” likely mismatches the checkbox input idCheckbox id is "prefer-ai-summary" but Label targets "prefer-ai-summary-input". This breaks label–control association (a11y and click target). If Checkbox forwards id to the underlying input, align them:
- <Label for="prefer-ai-summary-input"> + <Label for="prefer-ai-summary">web-next/src/components/SettingsTabs.tsx (1)
9-9
: New Invite tab wired correctly; consider avoiding hard-coded grid column countThe tab, href, and union type update look good. To make the layout resilient if tabs change, avoid grid-cols-4:
- <TabsList class="grid max-w-prose mx-auto grid-cols-4"> + <TabsList class="grid max-w-prose mx-auto grid-flow-col auto-cols-fr">Also applies to: 31-31, 46-52
graphql/misc.ts (1)
7-24
: Cache availableLocales to avoid filesystem work on every queryThis list is static at runtime; memoize it to reduce I/O.
+let cachedLocales: Intl.Locale[] | null = null; builder.queryField("availableLocales", (t) => t.field({ type: ["Locale"], async resolve(_root, _args, _ctx) { - const availableLocales: Intl.Locale[] = []; + if (cachedLocales) return cachedLocales; + const availableLocales: Intl.Locale[] = []; const files = expandGlob(join(LOCALES_DIR, "*.json"), { includeDirs: false, }); for await (const file of files) { if (!file.isFile) continue; const match = file.name.match(/^(.+)\.json$/); if (match == null) continue; const localeName = match[1]; - availableLocales.push(new Intl.Locale(localeName)); + try { + availableLocales.push(new Intl.Locale(localeName)); + } catch { + // ignore invalid tags + } } - return availableLocales; + cachedLocales = availableLocales; + return availableLocales; }, }));web-next/src/routes/(root)/[handle]/settings/invite.tsx (2)
231-233
: UX copy nits and validation clarity.
- “Invite your friends… You can invite up to # [people/person]” reads like a quota cap, but you’re displaying remaining invitations. Consider “You can invite # more [people/person].”
- When invalid, you flip to “invalid” state but still render the generic description via the first Match when
emailError() == null
. Looks good, just ensureTextFieldDescription
is hidden on error as you already do.Also applies to: 247-252, 264-295, 322-332
146-147
: Use a stable verifyUrl template origin for SSR/preload contexts.
window.location.origin
will throw during SSR or non-DOM preloads. Prefer deriving origin from config or request context on the server and passing it in, or guard for client-only usage.- verifyUrl: `${window.location.origin}/sign/up/{token}?code={code}`, + verifyUrl: `${location.origin ?? globalThis.location?.origin ?? ""}/sign/up/{token}?code={code}`,Or inject the base URL from server config at build/runtime.
web-next/src/components/LocaleSelect.tsx (2)
22-31
: Memoize available locales to avoid double reactive reads.You read
availableLocales()?.availableLocales
twice. Cache it once for cleaner reactivity.export function LocaleSelect(props: LocaleSelectProps) { const { i18n } = useLingui(); const availableLocales = createFragment( @@ ); + const list = () => availableLocales()?.availableLocales ?? []; return ( <Select value={toLocaleInfo( negotiateLocale( props.value, - availableLocales()?.availableLocales ?? [], + list(), )?.baseName ?? "en", i18n.locale, )} onChange={(o) => props.onChange(o?.code ?? "en")} options={mapLocaleInfo( - availableLocales()?.availableLocales ?? [], + list(), i18n.locale, )}Also applies to: 33-46
105-118
: Guard DisplayNames fallbacks for uncommon locales.
Intl.DisplayNames
may return undefined; you already fall back tol
. Consider trimming region/scripts for nicer names ifdisplayNames.of(l)
is undefined.graphql/invite.ts (2)
236-265
: Cache locale templates to avoid disk I/O on every invite.
getEmailTemplate
scans and reads files on each call. CacheavailableLocales
and parsed JSON per locale in a module-level Map.
220-227
: Consider crediting back an invitation when email send fails.You log failures but still consume the invite. Depending on product policy, you may want to roll back the decrement or surface an error result.
graphql/schema.graphql (1)
494-497
: Fix typo in docstrings (“variabvles” → “variables”).Minor but user-facing in schema docs.
- The RFC 6570-compliant URI Template for the verification link. Available variabvles: `{token}` and `{code}`. + The RFC 6570-compliant URI Template for the verification link. Available variables: `{token}` and `{code}`.Also applies to: 513-516
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
deno.lock
is excluded by!**/*.lock
📒 Files selected for processing (14)
graphql/invite.ts
(1 hunks)graphql/locales/en.json
(1 hunks)graphql/locales/ja.json
(1 hunks)graphql/locales/ko.json
(1 hunks)graphql/locales/zh-CN.json
(1 hunks)graphql/locales/zh-TW.json
(1 hunks)graphql/misc.ts
(1 hunks)graphql/mod.ts
(1 hunks)graphql/schema.graphql
(3 hunks)models/i18n.ts
(2 hunks)web-next/src/components/LocaleSelect.tsx
(1 hunks)web-next/src/components/SettingsTabs.tsx
(3 hunks)web-next/src/routes/(root)/[handle]/settings/invite.tsx
(1 hunks)web-next/src/routes/(root)/[handle]/settings/preferences.tsx
(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
graphql/misc.ts (1)
models/i18n.ts (1)
Locale
(30-30)
web-next/src/routes/(root)/[handle]/settings/invite.tsx (6)
web-next/src/routes/(root)/[handle]/settings/preferences.tsx (1)
route
(36-43)web-next/src/components/Title.tsx (1)
Title
(7-14)web-next/src/components/ProfilePageBreadcrumb.tsx (1)
ProfilePageBreadcrumb
(16-49)web-next/src/components/SettingsTabs.tsx (1)
SettingsTabs
(16-65)web-next/src/components/Trans.tsx (1)
Trans
(22-42)web-next/src/components/LocaleSelect.tsx (1)
LocaleSelect
(22-89)
web-next/src/components/LocaleSelect.tsx (1)
models/i18n.ts (1)
negotiateLocale
(173-225)
graphql/invite.ts (6)
models/uuid.ts (1)
Uuid
(4-4)models/i18n.ts (2)
Locale
(30-30)negotiateLocale
(173-225)models/account.ts (1)
normalizeEmail
(522-536)models/schema.ts (1)
accountTable
(41-96)models/signup.ts (2)
createSignupToken
(36-64)SignupToken
(23-29)graphql/email.ts (1)
EMAIL_FROM
(11-11)
🪛 Biome (2.1.2)
web-next/src/routes/(root)/[handle]/settings/invite.tsx
[error] 54-54: Shouldn't redeclare 'invitePageQuery'. Consider to delete it or rename it.
'invitePageQuery' is defined here:
(lint/suspicious/noRedeclare)
[error] 82-82: Shouldn't redeclare 'inviteMutation'. Consider to delete it or rename it.
'inviteMutation' is defined here:
(lint/suspicious/noRedeclare)
🔇 Additional comments (12)
graphql/locales/zh-TW.json (1)
6-9
: Invite templates look good and placeholders are consistent.Subject/body read naturally, placeholders (inviter, inviterName, verifyUrl, expiration, message) match the expected set.
graphql/locales/ja.json (1)
6-9
: Japanese invite strings read well; placeholders are correct.No functional or linguistic blockers spotted.
graphql/locales/en.json (1)
6-9
: English invite copy is clear; placeholders align across locales.Good consistency with other languages and expected variables.
models/i18n.ts (2)
156-160
: Accepting readonly arrays is a solid API improvement.Safer contracts with no behavior change. 👍
168-176
: Unify overloads on readonly arrays—good type hygiene.Signatures are consistent and non-mutating across overloads.
web-next/src/routes/(root)/[handle]/settings/preferences.tsx (1)
214-224
: Spacing tweak LGTMSwitching to gap-1.5 tightens density nicely without layout risk.
Also applies to: 225-236
graphql/mod.ts (1)
7-7
: Registering invite and misc modules — looks goodSide-effect imports ensure the new schema pieces are included during builder init. No ordering concerns spotted.
Also applies to: 9-9
graphql/locales/zh-CN.json (1)
6-10
: Invite templates added — placeholder parity verified across locales
All locale files now define the same placeholders (expiration, inviter, inviterName, message, verifyUrl).graphql/schema.graphql (4)
387-394
: Invitation type addition looks good.Fields are minimal and match resolver output.
405-406
: Union definition LGTM.
InviteResult
union correctly matches resolver’s discriminants.
477-487
: Docstring OK; ensure parity with backend template variables.The available variables list matches resolver checks for
{token}
and{code}
.
788-789
: Query.availableLocales addition matches frontend usage.Works with LocaleSelect fragment.
Fill in missing translations for invitation functionality across all supported locales (ja-JP, ko-KR, zh-CN, zh-TW). Translations include invitation form labels, error messages, validation feedback, and UI elements. All translations follow established terminology from glossary files and maintain consistency with existing locale conventions. Co-Authored-By: Claude <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web-next/src/locales/zh-TW/messages.po (1)
627-629
: Typo: duplicated word “通行通行金鑰”User-facing typo; fix to single “通行金鑰”.
-msgstr "已註冊的通行通行金鑰" +msgstr "已註冊的通行金鑰"
🧹 Nitpick comments (16)
web-next/src/locales/ko-KR/messages.po (3)
218-221
: Use “선호하는” to better match “prefers”Minor tone tweak to mirror the English nuance.
-msgstr "초대장을 받을 친구가 사용하는 언어를 선택하세요. 이 언어는 초대장에만 사용됩니다." +msgstr "초대장을 받을 친구가 선호하는 언어를 선택하세요. 이 언어는 초대장에만 사용됩니다."
739-742
: Fix spacing/comma around “뿐만 아니라”Korean grammar prefers 붙여쓰기 and no comma here.
-msgstr "이메일 주소는 초대장을 받을 때 뿐만 아니라, 계정에 로그인할 때도 사용됩니다." +msgstr "이메일 주소는 초대장을 받을 때뿐만 아니라 계정에 로그인할 때도 사용됩니다."
14-14
: Add explicit Plural-Forms header for better PO tooling compatibilitySome tools warn when this is empty; Korean should declare a single plural form.
-Plural-Forms: +Plural-Forms: nplurals=1; plural=0;web-next/src/locales/zh-TW/messages.po (11)
26-30
: Clarify remaining-invites and use natural counter (“位”)Aligns better with invitationsLeft and reads more native.
-msgstr "{0, plural, other {邀請你的朋友加入 Hackers' Pub。你可以邀請最多 # 個人。}}" +msgstr "{0, plural, other {邀請你的朋友加入 Hackers' Pub。你還可以邀請 # 位朋友。}}"
173-176
: More natural phrasing for “unexpected error”Minor tone improvement for zh-TW.
-msgstr "發生了意外錯誤。請稍後再試。" +msgstr "發生未預期的錯誤。請稍後再試。"
218-221
: Clarify scope to the invitation email and use “偏好”Improves clarity and tone.
-msgstr "選擇你的朋友使用的語言。這個語言只會用於邀請。" +msgstr "選擇你的朋友偏好的語言。此語言僅用於邀請信。"
391-394
: Consider “邀請信語言” for precisionMakes it obvious it’s for the email.
-msgstr "邀請語言" +msgstr "邀請信語言"
395-398
: Prefer TW-local “寄出” over “發送”Sounds more native in zh-TW UI.
-msgstr "邀請已發送" +msgstr "邀請已寄出"
653-656
: Button copy: “送出” is more idiomatic than “發送”Common in zh-TW interfaces.
-msgstr "發送" +msgstr "送出"
657-660
: Progress copy: match button with “送出中…”Keeps wording consistent.
-msgstr "發送中…" +msgstr "送出中…"
739-742
: Tighten the subject and flowAdd “此” and parallel structure for clarity.
-msgstr "電子郵件地址不僅用於接收邀請,還用於登入帳戶。" +msgstr "此電子郵件地址不僅用於接收邀請,也將用於登入帳戶。"
747-750
: Align success toast with “寄出”Keeps terminology consistent with the send action.
-msgstr "邀請已成功發送。" +msgstr "邀請已成功寄出。"
886-889
: Unify with earlier term “邀請信”Keep the same noun throughout the invite flow.
-msgstr "你的朋友將在邀請郵件中看到此訊息。" +msgstr "你的朋友將在邀請信中看到此訊息。"
610-613
: Unify “Code of conduct” termElsewhere it’s “行為準則”; keep it consistent.
-msgstr "閱讀完整的行為守則" +msgstr "閱讀完整的行為準則"web-next/src/locales/ja-JP/messages.po (2)
26-30
: Align copy with “remaining invites” count (uses invitationsLeft).The placeholder comment says invitationsLeft; “最大#人” reads as a quota cap, not remaining. Suggest “あと#人招待できます。”
-msgstr "{0, plural, one {友達をHackers' Pubに招待しましょう。最大#人まで招待できます。} other {友達をHackers' Pubに招待しましょう。最大#人まで招待できます。}}" +msgstr "{0, plural, one {友達をHackers' Pubに招待しましょう。あと#人招待できます。} other {友達をHackers' Pubに招待しましょう。あと#人招待できます。}}"
218-221
: Use “招待メール” consistently (avoid “招待状”) and tighten phrasing.The flow explicitly sends email invitations; “招待メール” is clearer and consistent with other keys.
-msgstr "招待する友達の使用言語を選択してください。この言語は招待状にのみ使用されます。" +msgstr "友達が希望する言語を選択してください。この言語は招待メールにのみ使用されます。" -msgstr "招待状の送信に失敗しました" +msgstr "招待メールの送信に失敗しました" -msgstr "招待状の言語" +msgstr "招待メールの言語" -msgstr "招待状を送信しました" +msgstr "招待メールを送信しました" -msgstr "招待状が残っていません" +msgstr "招待枠は残っていません" -msgstr "メールアドレスは招待状を受け取るだけでなく、アカウントへのログインにも使用されます。" +msgstr "メールアドレスは招待メールの受信だけでなく、アカウントへのログインにも使用されます。" -msgstr "招待状が正常に送信されました。" +msgstr "招待メールが送信されました。" -msgstr "招待状が残っていません。追加されるまでお待ちください。" +msgstr "招待枠はありません。追加されるまでお待ちください。" -msgstr "友達は招待メールでこのメッセージを見ることができます。" +msgstr "友達は招待メールでこのメッセージを確認できます。"Also applies to: 333-337, 391-394, 395-398, 497-500, 739-742, 747-750, 857-861, 886-889
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
web-next/src/locales/en-US/messages.po
(14 hunks)web-next/src/locales/ja-JP/messages.po
(14 hunks)web-next/src/locales/ko-KR/messages.po
(14 hunks)web-next/src/locales/zh-CN/messages.po
(14 hunks)web-next/src/locales/zh-TW/messages.po
(14 hunks)
✅ Files skipped from review due to trivial changes (2)
- web-next/src/locales/en-US/messages.po
- web-next/src/locales/zh-CN/messages.po
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: image (ubuntu-latest)
- GitHub Check: image (ubuntu-24.04-arm)
🔇 Additional comments (27)
web-next/src/locales/ko-KR/messages.po (16)
26-30
: Plural ICU for ko handled correctly — OKSingle-form translation with “other {…}” is appropriate for Korean; placeholders preserved.
73-77
: Placeholder preserved and natural phrasing — OKReads idiomatically: “{0} 님은 이미 … 회원입니다.”
173-176
: Generic error copy is clean and polite — OK
266-270
: Reference added for invite page — OKString and translation remain correct.
287-290
: Label translation is concise and correct — OK
333-337
: Failure copy is clear — OK
391-404
: Invite-related labels/messages are consistent — OK“초대장 언어”, “초대장이 발송되었습니다”, “초대” all consistent with other invite strings.
565-568
: Unrelated reference shift (SettingsTabs) — no action
573-576
: Form error guidance reads naturally — OK
653-660
: CTA and progress states look good — OK
661-667
: “Settings” reference addition — OK
735-738
: Validation message is accurate — OK
747-750
: Success message is clear — OK
849-852
: Optional field hint is clear — OK
857-861
: Quota-exhausted copy is consistent with other strings — OK
886-889
: Explanatory helper text reads well — OKweb-next/src/locales/zh-TW/messages.po (9)
73-77
: LGTM — accurate and idiomaticTranslation reads well and preserves meaning.
266-270
: LGTM — “Email address” is correctTerm choice is standard in zh-TW.
287-290
: LGTM — “Extra message” OKClear and idiomatic.
333-337
: LGTM — failure toastConsistent with other error messages.
399-404
: LGTM — tab labelConcise and correct.
497-500
: LGTM — quota messageTerm “邀請名額” is clear.
573-576
: LGTM — validation promptTone and wording are good.
735-738
: LGTM — invalid emailClear and consistent with other validations.
849-852
: LGTM — helper textNatural and succinct.
web-next/src/locales/ja-JP/messages.po (2)
1-15
: Set correct Plural-Forms header for Japanese.Prevents gettext tooling warnings and keeps PO metadata complete.
-"Plural-Forms: \n" +"Plural-Forms: nplurals=1; plural=0;\n"
653-660
: LGTM for these additions.Natural translations and correct tone/placeholders.
Also applies to: 399-404, 287-290, 849-852, 173-176, 266-270, 735-738
- Add caching for availableLocales query to avoid filesystem operations on every request - Add error handling for invalid locale tags in misc.ts - Implement email template caching to reduce disk I/O overhead - Preload all locale templates at startup for better performance Co-Authored-By: Claude <[email protected]>
- Fix Korean spelling error in email template: "메세지" → "메시지" - Fix Traditional Chinese typo: "通行通行金鑰" → "通行金鑰" - Optimize negotiateLocale performance by precomputing maximize() results - Fix accessibility issue with label for attribute mismatch Co-Authored-By: Claude <[email protected]>
- Replace window.location.origin with globalThis.location?.origin fallback - Prevents SSR errors when window is not available - Provides fallback URL for server-side rendering contexts Co-Authored-By: Claude <[email protected]>
- Add INVITER_EMAIL_SEND_FAILED to InviteInviterError enum - Auto-generated from graphql/invite.ts enum changes - Supports proper error handling for email send failures Co-Authored-By: Claude <[email protected]>
- Add invitation quota rollback when email sending fails - Implement INVITER_EMAIL_SEND_FAILED error type for proper user feedback - Add email template caching to reduce disk I/O overhead - Add error handling for invalid locale tags in availableLocales query Co-Authored-By: Claude <[email protected]>
133ffb6
to
aff5ea6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web-next/src/locales/ja-JP/messages.po (1)
791-794
: Fix typos: ユーサー → ユーザー (twice).User-facing string contains a misspelling.
Apply:
- msgstr "他のユーサーが使用しているユーサー名です。" + msgstr "他のユーザーが使用しているユーザー名です。"
♻️ Duplicate comments (3)
web-next/src/routes/(root)/[handle]/settings/invite.tsx (2)
145-145
: Avoid double trim; use fallback to null.Slightly simpler and avoids duplicate work.
- message: message().trim() === "" ? null : message().trim(), + message: message().trim() || null,
54-71
: Fix Biome noRedeclare by renaming GraphQL document constants.Identifiers collide with imported types (invitePageQuery, inviteMutation), blocking CI.
Apply:
-const invitePageQuery = graphql` +const InvitePageQueryNode = graphql` query invitePageQuery($username: String!) { @@ -const loadInvitePageQuery = query( +const loadInvitePageQuery = query( (handle: string) => loadQuery<invitePageQuery>( useRelayEnvironment()(), - invitePageQuery, + InvitePageQueryNode, { username: handle.replace(/^@/, "") }, ), "loadInvitePageQuery", ); @@ -const inviteMutation = graphql` +const InviteMutationNode = graphql` mutation inviteMutation( @@ } `; @@ const data = createPreloadedQuery<invitePageQuery>( - invitePageQuery, + InvitePageQueryNode, () => loadInvitePageQuery(params.handle), ); @@ - const [send] = createMutation<inviteMutation>(inviteMutation); + const [send] = createMutation<inviteMutation>(InviteMutationNode);Also applies to: 72-80, 82-114, 120-123, 136-137
graphql/invite.ts (1)
194-196
: Gate on verifyUrl errors; remove duplicated email check.Currently
errors.email
is checked twice anderrors.verifyUrl
is ignored, allowing bad templates to proceed.Apply:
- if ( - errors.inviter != null || errors.email != null || - errors.email != null || ctx.account == null || email == null - ) { + if ( + errors.inviter != null || + errors.email != null || + errors.verifyUrl != null || + ctx.account == null || + email == null + ) { return errors; }
🧹 Nitpick comments (4)
web-next/src/routes/(root)/[handle]/settings/invite.tsx (1)
259-265
: Add email autofill hints.Improves UX and mobile keyboards.
- <TextFieldInput + <TextFieldInput type="email" required id="email" + autocomplete="email" + inputmode="email" placeholder="[email protected]"graphql/invite.ts (3)
216-223
: Normalize locale to Intl.Locale before use and return.Ensures type-safety for
getEmailMessage
/getEmailTemplate
and theInvitation.locale
field.- const message = await getEmailMessage({ - locale: args.locale, + const normalizedLocale = typeof args.locale === "string" + ? new Intl.Locale(args.locale) + : (args.locale as Intl.Locale); + const message = await getEmailMessage({ + locale: normalizedLocale, inviter: ctx.account, verifyUrlTemplate: args.verifyUrl, to: email, token, message: args.message ?? undefined, }); @@ return { inviterId: ctx.account.id, email, - locale: args.locale, + locale: normalizedLocale, message: args.message ?? undefined, };Also applies to: 240-245
337-340
: Provide a fallback when Temporal.Duration.toLocaleString is unsupported.Deno/Node runtimes may not support DurationFormat; add a robust fallback.
- const expiration = EXPIRATION.toLocaleString(locale.baseName, { - // @ts-ignore: DurationFormatOptions, not DateTimeFormatOptions - style: "long", - }); + const expiration = (() => { + const anyDuration = EXPIRATION as unknown as { toLocaleString?: (loc: string, opts?: unknown) => string }; + if (typeof anyDuration.toLocaleString === "function") { + // @ts-ignore DurationFormatOptions where supported + return anyDuration.toLocaleString(locale.baseName, { style: "long" }); + } + const hours = Math.round(EXPIRATION.total("hour")); + return `${hours} ${hours === 1 ? "hour" : "hours"}`; + })();
251-299
: Consider extracting locale-template caching to a shared utility.
loadEmailTemplates
duplicates concerns with the locales discovery query; a shared module avoids drift.I can draft a small
graphql/locales/cache.ts
and update both call-sites. Want a patch?Also applies to: 300-321
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (12)
graphql/invite.ts
(1 hunks)graphql/locales/ko.json
(1 hunks)graphql/misc.ts
(1 hunks)graphql/schema.graphql
(3 hunks)models/i18n.ts
(3 hunks)web-next/src/locales/en-US/messages.po
(14 hunks)web-next/src/locales/ja-JP/messages.po
(14 hunks)web-next/src/locales/ko-KR/messages.po
(14 hunks)web-next/src/locales/zh-CN/messages.po
(14 hunks)web-next/src/locales/zh-TW/messages.po
(15 hunks)web-next/src/routes/(root)/[handle]/settings/invite.tsx
(1 hunks)web-next/src/routes/(root)/[handle]/settings/preferences.tsx
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
- models/i18n.ts
- web-next/src/locales/zh-CN/messages.po
- graphql/misc.ts
- web-next/src/locales/zh-TW/messages.po
- web-next/src/locales/en-US/messages.po
- web-next/src/routes/(root)/[handle]/settings/preferences.tsx
- graphql/locales/ko.json
- graphql/schema.graphql
🧰 Additional context used
🧬 Code graph analysis (2)
web-next/src/routes/(root)/[handle]/settings/invite.tsx (6)
web-next/src/routes/(root)/[handle]/settings/preferences.tsx (1)
route
(36-43)web-next/src/components/Title.tsx (1)
Title
(7-14)web-next/src/components/ProfilePageBreadcrumb.tsx (1)
ProfilePageBreadcrumb
(16-49)web-next/src/components/SettingsTabs.tsx (1)
SettingsTabs
(16-65)web-next/src/components/Trans.tsx (1)
Trans
(22-42)web-next/src/components/LocaleSelect.tsx (1)
LocaleSelect
(22-89)
graphql/invite.ts (6)
models/uuid.ts (1)
Uuid
(4-4)models/i18n.ts (2)
Locale
(30-30)negotiateLocale
(173-226)models/account.ts (1)
normalizeEmail
(522-536)models/schema.ts (1)
accountTable
(41-96)models/signup.ts (2)
createSignupToken
(36-64)SignupToken
(23-29)graphql/email.ts (1)
EMAIL_FROM
(11-11)
🪛 Biome (2.1.2)
web-next/src/routes/(root)/[handle]/settings/invite.tsx
[error] 54-54: Shouldn't redeclare 'invitePageQuery'. Consider to delete it or rename it.
'invitePageQuery' is defined here:
(lint/suspicious/noRedeclare)
[error] 82-82: Shouldn't redeclare 'inviteMutation'. Consider to delete it or rename it.
'inviteMutation' is defined here:
(lint/suspicious/noRedeclare)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: image (ubuntu-24.04-arm)
- GitHub Check: image (ubuntu-latest)
🔇 Additional comments (3)
web-next/src/locales/ko-KR/messages.po (1)
26-30
: Korean invitation flow translations look good.Pluralization, error/success states, field labels, and helper texts read naturally and align with the UI and mutation behaviors.
Also applies to: 73-77, 173-176, 218-221, 266-270, 287-290, 333-337, 391-394, 395-398, 399-404, 497-500, 573-576, 653-656, 657-660, 735-742, 747-750, 849-852, 857-861, 886-889
web-next/src/locales/ja-JP/messages.po (1)
26-30
: Japanese invitation flow translations LGTM.Copy matches product terminology and pluralization is handled appropriately for ja-JP.
Also applies to: 73-77, 173-176, 218-221, 266-270, 287-290, 333-337, 391-394, 395-398, 399-404, 497-500, 573-576, 653-656, 657-660, 735-742, 747-750, 849-852, 857-861, 886-889
graphql/invite.ts (1)
224-239
: Good: credit back invitation on email send failure.Prevents silent quota loss and surfaces a clear validation error to the client.
Summary
This PR implements a user invitation system that allows existing users to invite new users to join the platform via email invitations.
Features
invite
mutation with comprehensive validationChanges
Backend
invite
mutation to GraphQL schema with error handlingFrontend (web-next)
LocaleSelect
component for language selection/settings/invite
Technical Details
Testing
The invitation system has been implemented with:
Screenshots
The new invite settings page provides a clean interface for sending invitations with:
Summary by CodeRabbit
New Features
UI
Localization
Style