feat(i18n): internationalization of mail app#414
feat(i18n): internationalization of mail app#414hiheyhello123 merged 23 commits intoMail-0:stagingfrom
Conversation
|
@nyzss is attempting to deploy a commit to the Zero Team on Vercel. A member of the Team first needs to authorize it. |
WalkthroughThe changes integrate internationalization throughout the mail application. Multiple components—including settings pages, mail display, conversation views, UI elements, and layout—now use the Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant RL as RootLayout
participant G as getLocale
participant GM as getMessages
participant NI as NextIntlClientProvider
participant C as Child Component
U->>RL: Request page
RL->>G: Fetch locale asynchronously
G-->>RL: Return locale
RL->>GM: Retrieve locale messages asynchronously
GM-->>RL: Return messages object
RL->>NI: Wrap children with translation context
NI->>C: Provide localization support via useTranslations
Possibly related PRs
Suggested reviewers
Poem
Tip ⚡🧪 Multi-step agentic review comment chat (experimental)
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (5)
apps/mail/components/mail/thread-display.tsx (1)
59-66: Consider localizing the static “Close” tooltip
Line 65 currently displays a hardcoded “Close” label inside<TooltipContent>. Although unchanged in this PR, localizing it would align with your broader i18n approach. For example:<TooltipContent>{t("common.actions.close")}</TooltipContent>apps/mail/i18n/utils.ts (1)
7-32: Implement robust locale detection.The
getLocalefunction correctly follows best practices by:
- First checking for user preference in cookies
- Falling back to the browser's accept-language header
- Providing a default locale as a last resort
However, the parsing logic for accept-language could be more robust.
// extract browser locale from accept-language header const acceptLanguage = headersList.get("accept-language"); if (acceptLanguage) { // it only needs to be the first part of the locale, ex: "en" for "en-US" - const reqLocale = acceptLanguage.split(",")[0]?.split("-")[0]?.trim(); + const reqLocale = acceptLanguage.split(",")[0]?.split("-")[0]?.trim() || ""; // check if the locale is supported - if (locales.includes(reqLocale as Locale)) { + if (reqLocale && locales.includes(reqLocale as Locale)) { return reqLocale; } }apps/mail/components/theme/theme-switcher.tsx (1)
61-65: Type-safe translation with dynamic theme keyThe component correctly uses the translation function with the current theme value as part of the key path.
Consider creating a helper function to ensure type safety for dynamic keys:
- {t(`common.themes.${theme}` as MessageKey)} + {t(`common.themes.${theme as 'dark' | 'light' | 'system'}` as MessageKey)}apps/mail/app/(routes)/settings/security/page.tsx (1)
15-17: Unnecessary import foundThe
KeyRoundicon is imported but not used in the component.- import { KeyRound } from "lucide-react";apps/mail/components/mail/mail.tsx (1)
49-53: Consider using optional chaining as suggested by static analysisThe static analysis tool suggests using optional chaining on line 56 for safer access to potentially undefined values.
- const filtered = items.filter((item) => item.tags && item.tags.includes(filterTag)); + const filtered = items.filter((item) => item.tags?.includes(filterTag));
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (30)
apps/mail/app/(routes)/settings/appearance/page.tsx(3 hunks)apps/mail/app/(routes)/settings/connections/page.tsx(5 hunks)apps/mail/app/(routes)/settings/general/page.tsx(8 hunks)apps/mail/app/(routes)/settings/security/page.tsx(5 hunks)apps/mail/app/(routes)/settings/shortcuts/page.tsx(2 hunks)apps/mail/app/layout.tsx(2 hunks)apps/mail/components/connection/add.tsx(2 hunks)apps/mail/components/context/command-palette-context.tsx(7 hunks)apps/mail/components/create/create-email.tsx(10 hunks)apps/mail/components/mail/mail-display.tsx(7 hunks)apps/mail/components/mail/mail.tsx(15 hunks)apps/mail/components/mail/reply-composer.tsx(11 hunks)apps/mail/components/mail/search-bar.tsx(15 hunks)apps/mail/components/mail/thread-display.tsx(6 hunks)apps/mail/components/theme/theme-switcher.tsx(2 hunks)apps/mail/components/ui/app-sidebar.tsx(5 hunks)apps/mail/components/ui/nav-main.tsx(3 hunks)apps/mail/components/ui/nav-user.tsx(5 hunks)apps/mail/config/navigation.ts(5 hunks)apps/mail/config/shortcuts.ts(1 hunks)apps/mail/global.d.ts(1 hunks)apps/mail/i18n/config.ts(1 hunks)apps/mail/i18n/request.ts(1 hunks)apps/mail/i18n/utils.ts(1 hunks)apps/mail/lib/constants.ts(1 hunks)apps/mail/messages/en.json(1 hunks)apps/mail/messages/fr.json(1 hunks)apps/mail/messages/tr.json(1 hunks)apps/mail/next.config.ts(2 hunks)apps/mail/package.json(2 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
apps/mail/components/ui/nav-main.tsx
[error] 211-211: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
apps/mail/components/mail/mail.tsx
[error] 56-56: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (99)
apps/mail/messages/fr.json (1)
1-209: Well-structured French translations with pluralization supportThe French translation file is well-organized with a comprehensive structure covering UI elements across different sections of the application. I particularly appreciate the inclusion of proper pluralization rules for attachments and file counts (lines 85-86), which is crucial for grammatical correctness in French.
apps/mail/lib/constants.ts (1)
1-1: Locale cookie constant follows established naming conventionThe new constant for storing locale preferences follows the same naming pattern as other constants in the file and uses a descriptive, namespaced value that clearly indicates its purpose.
apps/mail/global.d.ts (1)
1-8: Type-safe approach for i18n messagesThe TypeScript declaration extends the global
IntlMessagesinterface to include the structure of your English messages, providing type safety when accessing translation keys throughout the application. This is a best practice that helps prevent typos and ensures consistent message key usage.apps/mail/i18n/config.ts (1)
1-4: Clean configuration with type-safe locale definitionsThe configuration creates a type-safe union of supported locales derived from the string literals in the array, making it easy to extend with additional languages in the future while maintaining type safety throughout the application.
apps/mail/package.json (1)
56-56: Dependencies added correctly for internationalization.The addition of
deepmergeandnext-intllibraries provides the foundation for the internationalization implementation. The versions specified with caret (^) allow for compatible updates, which is good practice.Also applies to: 72-72
apps/mail/config/shortcuts.ts (1)
3-23: Good refactoring of action strings to camelCase.Converting the keyboard shortcut action strings to camelCase improves consistency and makes them more suitable as translation keys. This standardization aligns well with the internationalization implementation.
apps/mail/i18n/request.ts (1)
1-16: Well-structured internationalization configuration.The implementation correctly:
- Retrieves the current locale
- Dynamically imports locale-specific messages
- Uses English messages as a fallback
- Merges them with
deepmergeto ensure complete translationsThis approach ensures all UI text can be localized while maintaining fallbacks for missing translations.
apps/mail/next.config.ts (2)
1-1: Next.js configuration properly integrates next-intl.The integration of
next-intlwith Next.js is correctly implemented by importing the plugin and wrapping the existing configuration. This enables the internationalization features throughout the application.Also applies to: 48-50
6-11: Improved formatting of removeConsole config.The reformatting of the
removeConsoleconfiguration enhances readability while maintaining the same functionality.apps/mail/app/(routes)/settings/appearance/page.tsx (4)
10-10: Import ofnext-intllibrary added correctlyGood addition of the
next-intllibrary for internationalization support.
22-22: Translation hook initialized properlyThe
useTranslationshook is correctly initialized to enable i18n support throughout the component.
42-48: Settings card internationalized appropriatelyGreat work on replacing hardcoded strings with translation keys for the title, description, and footer button text. The conditional rendering for the button text based on
isSavingstate is also properly internationalized.
54-55: Theme label internationalized and styling improvedGood job on replacing the hardcoded label text with a translation reference. The background class added to the
ModeTogglecomponent improves visual consistency.apps/mail/components/ui/app-sidebar.tsx (5)
12-16: Imports properly reorganized for i18n supportGood job adding the necessary imports for internationalization support while maintaining proper organization of your imports.
38-40: Improved code formatting for badge count assignmentThe logic for setting the badge counts has been reformatted for better readability, using the
FOLDERSconstant for consistency.
42-44: Consistent formatting applied to SENT folder badgeThe same formatting improvement has been applied consistently to the SENT folder badge calculation.
130-130: Translation hook added to ComposeButton componentThe
useTranslationshook is properly initialized in theComposeButtonfunction.
142-142: Create button text internationalizedGood replacement of the hardcoded "Create Email" text with a translation key.
apps/mail/components/connection/add.tsx (5)
6-6: Import ofnext-intllibrary added correctlyGood addition of the
next-intllibrary for internationalization support.
9-10: Translation hook initialized with proper spacingThe
useTranslationshook is correctly initialized with consistent spacing for better readability.
16-16: Add Email text internationalizedGood replacement of the hardcoded "Add email" text with a translation key.
21-22: Dialog title and description internationalizedExcellent replacement of hardcoded dialog title and description text with translation keys.
43-43: Coming Soon text internationalizedGood replacement of the hardcoded "More Coming Soon" text with a translation key.
apps/mail/components/mail/reply-composer.tsx (8)
1-9: Imports reorganized andnext-intladdedThe imports have been properly reorganized and the necessary
useTranslationsimport has been added for internationalization support.
29-29: Translation hook initialized with namespaceThe
useTranslationshook is correctly initialized with the "common.replyCompose" namespace, providing better organization for translation keys.
44-44: String quote style standardizedThe code style has been improved by using double quotes consistently for string literals, which is a good practice for better readability.
Also applies to: 69-70, 77-78, 85-86
201-212: Empty message check reformatted for clarityThe condition to check for empty messages has been reformatted to be more readable, making the code easier to understand and maintain.
226-228: Reply button text internationalizedGood job replacing the hardcoded "Reply to" text with translation keys that handle both the action and the recipient dynamically.
251-254: Drag and drop area text internationalizedThe text for the file drop area has been properly internationalized while maintaining the styling.
317-317: Attachment-related texts internationalized with count parameterExcellent implementation of internationalized text for attachments, including dynamic count parameters that will support proper pluralization in different languages.
Also applies to: 327-327, 334-334, 336-336
379-379: Action buttons internationalizedGood job internationalizing the "Save Draft" and "Send" button texts, which are critical user interface elements.
Also applies to: 381-382, 392-392
apps/mail/components/mail/thread-display.tsx (5)
21-26: Add import statements forReplyIconanduseTranslations
The newly introduced imports forReplyIconanduseTranslationsare appropriate to support the animated reply icon and internationalized strings. This aligns well with the i18n enhancements in the rest of your code.
89-89: Minor UI adjustment for<ArchiveIcon>
Using"relative top-0.5"shifts the icon slightly, which can help with visual balance. This looks fine and doesn’t introduce any functional issues.
199-199: Properly initialize translations
CallinguseTranslations()and storing it intis correct for retrieving localized strings throughout the component.
242-295: Internationalization of thread actions in loading skeleton
These lines showcase a good practice of replacing hardcoded labels (e.g., "Close", "Archive", "Reply") with translation keys, ensuring support for multiple locales during the loading state. This approach is consistent and maintains accessibility by pairing each icon with an accessible label.
331-383: Internationalization of thread actions in fully loaded state
All action labels (e.g., "close", "exitFullscreen", "enterFullscreen", "archive", "reply", "moreOptions", "moveToSpam", etc.) now correctly reference translation keys. This is an effective use ofnext-intland ensures a seamless experience for multiple locales.apps/mail/components/ui/nav-user.tsx (2)
31-46: InitializeuseTranslationshook
ImportinguseTranslationsand assigning it totis the standard approach for retrieving localized strings. This is consistent with the rest of your i18n integration.
147-225: Internationalizing menu items
All references to strings such as “customerSupport”, “documentation”, “appTheme”, “accounts”, “logout”, “signIn”, etc. now use translation keys, which is essential for a multilingual experience. This change is implemented cleanly and matches your overall internationalization strategy.apps/mail/app/layout.tsx (2)
1-4: Importing i18n utilities
UsinggetLocaleandgetMessagesfrom"next-intl/server"plusNextIntlClientProviderfrom"next-intl"sets the foundation for server-side internationalization. This step is crucial for ensuring the client receives the correct locale and message data.
24-39: Adopting an async RootLayout with locale-based<html>
ShiftingRootLayoutto async to retrieve the user’s locale and messages is a recommended approach for server-driven i18n. Dynamically settinglang={locale}in the<html>tag and wrapping content inNextIntlClientProviderensures all child components have access to the correct locale and translations.apps/mail/components/ui/nav-main.tsx (2)
8-13: Imports reorganized to support internationalization.The imports are now better organized, with the addition of internationalization support through the
useTranslationshook from next-intl. The addition of MessageKey type will help ensure type safety when using translation keys.
179-180: Added translation hook for internationalization.The useTranslations hook has been properly added to access translation functions.
apps/mail/components/context/command-palette-context.tsx (6)
67-68: Added translation hook.The useTranslations hook has been correctly added to support internationalization in the CommandPalette component.
93-100: Translated command group labels.Hardcoded group labels have been replaced with translation keys, which is essential for proper internationalization.
103-117: Improved filtering logic with translations.The filtering logic has been updated to use translations for comparisons and restructured for better readability.
136-141: Translated dialog elements.The dialog title, description, input placeholder, and empty state message have been properly internationalized.
184-201: Translated help section.All help-related text has been properly internationalized, including the group heading and command descriptions.
174-174:Details
❓ Verification inconclusive
Verify translation key consistency.
Unlike in nav-main.tsx where
item.titleis cast toMessageKey, here it's used directly without type casting. Verify that allitem.titlevalues are valid translation keys to prevent runtime errors.
🏁 Script executed:
#!/bin/bash # Check for any places where item.title might be used with t() function rg -A 1 -B 1 "t\(item\.title\)" --glob "*.tsx" --glob "*.ts" # Check for MessageKey imports and usages rg "MessageKey" --glob "*.tsx" --glob "*.ts"Length of output: 1830
Action Required: Verify Translation Key Usage in Command Palette Context
Inapps/mail/components/context/command-palette-context.tsxon line 174, the translation function is invoked witht(item.title)without casting toMessageKey. In contrast, other parts of the codebase (e.g., innav-main.tsx) explicitly cast translation keys (i.e., usingas MessageKey). Please verify thatitem.titlein this context is always a valid translation key. If there’s any doubt, consider applying the same explicit type cast to ensure type safety and avoid potential runtime errors.apps/mail/i18n/utils.ts (1)
34-37: Simple and effective locale change function.The
changeLocalefunction is well implemented, taking the new locale and updating it in the cookie store.apps/mail/messages/en.json (2)
1-210: Well-structured translation file.The English translations are organized in a logical, hierarchical structure that makes it easy to locate specific keys. The file includes:
- Common UI elements and actions
- Navigation items and settings
- Page-specific content
The use of ICU MessageFormat for pluralization on lines 86-87 is particularly good practice for internationalization.
86-88: Correctly implemented pluralization.The use of ICU MessageFormat for plurals is excellent:
"attachmentCount": "{count, plural, =0 {attachments} one {attachment} other {attachments}}", "fileCount": "{count, plural, =0 {files} one {file} other {files}}",This ensures proper grammar regardless of the count value.
apps/mail/config/navigation.ts (4)
13-14: Good implementation of type-safe translationsThe introduction of
MessageKeytype usingMessageKeysandNestedKeyOffrom next-intl provides good type safety for translation keys. This will help catch typos and missing translations at compile time.Also applies to: 24-24
36-36: Helpful developer commentThe comment clearly instructs developers that navigation item titles must be message keys, which helps maintain consistency across the codebase as internationalization is implemented.
45-78: Consistent internationalization of navigation itemsAll hardcoded strings have been properly replaced with translation keys, following a structured pattern. This is a good approach for maintainable internationalization.
132-162: Well-structured i18n keys for settings navigationThe settings navigation items have been properly internationalized with a consistent naming pattern (
navigation.settings.*), which matches the structure in the translation files.apps/mail/components/theme/theme-switcher.tsx (2)
12-14: Properly set up internationalization hooksThe component correctly imports and initializes the
useTranslationshook from next-intl, and imports theMessageKeytype for type safety.Also applies to: 24-24
73-85: Consistent translations for theme optionsAll theme options (dark, system, light) now use translations instead of hardcoded text, creating a consistent user experience.
apps/mail/app/(routes)/settings/security/page.tsx (3)
51-53: Properly internationalized title and descriptionsThe title and description for the security settings card now correctly use the translation function.
Also applies to: 57-57
55-57: Internationalized button text with conditional renderingThe button text now uses translations and correctly handles the conditional rendering based on the
isSavingstate.
69-95: Comprehensive internationalization of form fieldsAll form labels and descriptions have been properly internationalized with appropriate translation keys.
apps/mail/messages/tr.json (2)
1-209: Well-structured Turkish translation fileThe translation file is comprehensive and well-organized with clear hierarchical sections for:
- Common UI elements (actions, themes, command palette)
- Navigation items
- Page-specific content
- Form fields and descriptions
The structure matches the translation keys used in the components, which ensures consistent translations across the application.
84-86: Good use of plural formsThe translation correctly implements plural forms using the ICU message format. This ensures grammatically correct translations for different quantities.
apps/mail/components/create/create-email.tsx (7)
10-10: Added support for internationalization with next-intlThe import of
useTranslationsfrom next-intl is a good step for implementing internationalization.
100-100: Good initialization of the translation hookThe
useTranslationshook is properly implemented at the component level to access translation strings.
232-232: Replaced hardcoded text with translation keyCorrectly using the translation function for the file drop instruction.
246-247: Properly internationalized form labelsAll form labels and section headings have been updated to use translation keys, maintaining a consistent approach to internationalization throughout the component.
Also applies to: 292-293, 305-306, 337-338
270-271: Effectively internationalized placeholder textsPlaceholder texts have been successfully updated to use translation keys, which will provide a better user experience for non-English speakers.
Also applies to: 297-298, 312-313
329-330: Correctly implemented pluralized translationsGood use of the count parameter in the translation function for proper pluralization of "file" based on the attachment count.
Also applies to: 340-342
384-385: Successfully internationalized button textsButton texts have been updated to use translation keys, completing the internationalization of all user-facing strings in the component.
Also applies to: 406-407
apps/mail/app/(routes)/settings/shortcuts/page.tsx (5)
5-7: Added necessary imports for internationalizationCorrectly imported both
MessageKeyfor type safety anduseTranslationsfor accessing translated strings.
12-12: Properly initialized translation hookThe translation hook is correctly set up at the component level.
17-19: Successfully internationalized card title and buttonsThe card title, description, and button texts have been properly updated to use translation keys.
Also applies to: 21-23
29-30: Smartly implemented dynamic translation keys for shortcutsGood approach using template literals with the action name to dynamically generate the correct translation key for each shortcut action, with proper type casting to
MessageKey.
40-40: Minor class ordering adjustmentsThe class name reorganization provides better readability without changing the styling.
Also applies to: 46-47
apps/mail/components/mail/mail-display.tsx (7)
3-4: Added and reorganized imports for internationalizationThe imports have been reorganized and
useTranslationshas been added to support internationalization.Also applies to: 7-7, 10-10
83-91: Improved code formatting for better readabilityThe ternary operation for
datahas been properly reformatted with better indentation for improved readability.
91-91: Correctly implemented scoped translationsGood use of the namespaced translation hook with "common.mailDisplay" to keep translations organized and accessible.
139-140: Successfully internationalized email details sectionAll labels in the email details popover have been properly updated to use translation keys, providing a consistent multilingual experience.
Also applies to: 148-149, 159-160, 171-172, 177-178, 183-184, 189-192
104-105: Improved component structure and class organizationThe component's structure and class organization have been improved for better readability and maintainability without affecting functionality.
Also applies to: 107-118, 119-120, 122-126, 128-130
198-205: Enhanced conditional styling with the cn utilityGood use of the cn utility for conditional class application based on the collapsed state.
210-229: Improved conditional rendering of AI summary popupThe conditional rendering of the AI summary popup has been reformatted for better readability while maintaining the same functionality.
apps/mail/app/(routes)/settings/general/page.tsx (7)
25-28: Added essential imports for i18n functionalityProperly imported the necessary i18n utilities including
changeLocale,useTranslations, and the availablelocales.
34-34: Enhanced type safety for language selectionChanged language validation from a simple string to a typed enum using the available locales, which improves type safety and ensures only valid languages can be selected.
59-60: Implemented locale change on form submissionGood addition of the
changeLocalefunction call when submitting the form to apply the selected language immediately.
85-86: Properly initialized translation hookThe translation hook is correctly set up at the component level.
90-92: Successfully internationalized card title and action buttonsThe card title, description, and button texts have been properly updated to use translation keys for a consistent multilingual experience.
Also applies to: 96-97, 99-100
112-113: Internationalized form labels and expanded language optionsForm labels have been updated to use translation keys, and French and Turkish language options have been added to match the PR objectives.
Also applies to: 122-124, 135-136
163-171: Thoroughly internationalized toggle settingsToggle settings for dynamic content and external images have been fully internationalized, with both labels and descriptions using translation keys.
Also applies to: 182-190
apps/mail/components/mail/search-bar.tsx (3)
29-29: Good implementation of internationalization hooksThe addition of
useTranslationsfrom next-intl and its implementation in both the main component and theDateFiltercomponent is well structured.Also applies to: 31-32, 102-103
56-56: Well implemented translation for date range pickerThe hardcoded text has been properly replaced with a translation key.
550-550: Good job consistently implementing translations throughout the componentAll UI text elements have been properly updated to use translation keys, maintaining consistency throughout the search bar component and its child elements.
Also applies to: 568-568, 583-583, 595-595, 603-603, 611-611, 619-619, 628-628, 648-649, 659-660, 668-669, 676-676, 686-686, 699-699, 710-710, 720-720, 731-731, 743-743, 751-751
apps/mail/components/mail/mail.tsx (3)
25-25: Good implementation of internationalization hookThe
useTranslationshook is properly imported and implemented in theMailCategoryTabscomponent.Also applies to: 448-449
398-434: Well implemented category translationsAll category names have been updated to use translation keys instead of hardcoded strings, following good internationalization practices.
550-551: Good implementation of dynamic translation renderingThe component properly renders translated category names using the translation function with appropriate type casting to
MessageKey.Also applies to: 556-557, 585-586
apps/mail/app/(routes)/settings/connections/page.tsx (5)
21-21: Good implementation of internationalization hookThe
useTranslationshook is properly imported and implemented in the component.Also applies to: 31-31
36-36: Well implemented translation for toast messagesSuccess and error toast messages are properly translated, enhancing the user experience for non-English speakers.
Also applies to: 41-41
47-50: Good implementation of translated card title and descriptionThe SettingsCard component now uses translated strings instead of hardcoded text.
138-141: Well implemented dialog text translationsAll dialog text elements have been properly updated to use translation keys, providing a consistent internationalized experience.
Also applies to: 145-152
162-162: Clean implementation of self-closing componentThe
AddConnectionDialogcomponent is properly implemented as a self-closing tag with appropriate props.
| { | ||
| "common": { | ||
| "actions": { | ||
| "logout": "Logout", | ||
| "back": "Back", | ||
| "create": "Create Email", | ||
| "saveChanges": "Save changes", | ||
| "saving": "Saving...", | ||
| "resetToDefaults": "Reset to Defaults", | ||
| "close": "Close" | ||
| }, | ||
| "themes": { | ||
| "dark": "Dark", | ||
| "light": "Light", | ||
| "system": "System" | ||
| }, | ||
| "commandPalette": { | ||
| "title": "Command Palette", | ||
| "description": "Quick navigation and actions for Mail-0", | ||
| "placeholder": "Type a command or search...", | ||
| "noResults": "No results found", | ||
| "groups": { | ||
| "mail": "Mail", | ||
| "settings": "Settings", | ||
| "actions": "Actions", | ||
| "help": "Help", | ||
| "navigation": "Navigation" | ||
| }, | ||
| "commands": { | ||
| "goToInbox": "Go to Inbox", | ||
| "goToDrafts": "Go to Drafts", | ||
| "goToSent": "Go to Sent", | ||
| "goToSpam": "Go to Spam", | ||
| "goToArchive": "Go to Archive", | ||
| "goToBin": "Go to Bin", | ||
| "goToSettings": "Go to Settings", | ||
| "newEmail": "New Email", | ||
| "composeMessage": "Compose message", | ||
| "searchEmails": "Search Emails", | ||
| "toggleTheme": "Toggle Theme", | ||
| "backToMail": "Back to Mail", | ||
| "goToDocs": "Go to docs", | ||
| "helpWithShortcuts": "Help with shortcuts" | ||
| } | ||
| }, | ||
| "searchBar": { | ||
| "pickDateRange": "Pick a date or a range", | ||
| "search": "Search", | ||
| "clearSearch": "Clear search", | ||
| "advancedSearch": "Advanced search", | ||
| "quickFilters": "Quick filters", | ||
| "searchIn": "Search in", | ||
| "recipient": "Recipient", | ||
| "sender": "Sender", | ||
| "subject": "Subject", | ||
| "dateRange": "Date range", | ||
| "category": "Category", | ||
| "folder": "Folder", | ||
| "allMail": "All Mail", | ||
| "unread": "Unread", | ||
| "hasAttachment": "Has Attachment", | ||
| "starred": "Starred", | ||
| "applyFilters": "Apply filters", | ||
| "reset": "Reset" | ||
| }, | ||
| "navUser": { | ||
| "customerSupport": "Customer Support", | ||
| "documentation": "Documentation", | ||
| "appTheme": "App Theme", | ||
| "accounts": "Accounts", | ||
| "signIn": "Sign in" | ||
| }, | ||
| "mailCategories": { | ||
| "primary": "Primary", | ||
| "important": "Important", | ||
| "personal": "Personal", | ||
| "updates": "Updates", | ||
| "promotions": "Promotions", | ||
| "social": "Social" | ||
| }, | ||
| "replyCompose": { | ||
| "replyTo": "Reply to", | ||
| "thisEmail": "this email", | ||
| "dropFiles": "Drop files to attach", | ||
| "attachments": "Attachments", | ||
| "attachmentCount": "{count, plural, =0 {attachments} one {attachment} other {attachments}}", | ||
| "fileCount": "{count, plural, =0 {files} one {file} other {files}}", | ||
| "saveDraft": "Save draft", | ||
| "send": "Send" | ||
| }, | ||
| "mailDisplay": { | ||
| "details": "Details", | ||
| "from": "From", | ||
| "to": "To", | ||
| "date": "Date", | ||
| "mailedBy": "Mailed-By", | ||
| "signedBy": "Signed-By", | ||
| "security": "Security", | ||
| "standardEncryption": "Standard encryption (TLS)" | ||
| }, | ||
| "threadDisplay": { | ||
| "exitFullscreen": "Exit fullscreen", | ||
| "enterFullscreen": "Enter fullscreen", | ||
| "archive": "Archive", | ||
| "reply": "Reply", | ||
| "moreOptions": "More options", | ||
| "moveToSpam": "Move to spam", | ||
| "replyAll": "Reply all", | ||
| "forward": "Forward", | ||
| "markAsUnread": "Mark as unread", | ||
| "addLabel": "Add label", | ||
| "muteThread": "Mute thread" | ||
| } | ||
| }, | ||
| "navigation": { | ||
| "sidebar": { | ||
| "inbox": "Inbox", | ||
| "drafts": "Drafts", | ||
| "sent": "Sent", | ||
| "spam": "Spam", | ||
| "archive": "Archive", | ||
| "bin": "Bin", | ||
| "settings": "Settings" | ||
| }, | ||
| "settings": { | ||
| "general": "General", | ||
| "connections": "Connections", | ||
| "security": "Security", | ||
| "appearance": "Appearance", | ||
| "shortcuts": "Shortcuts" | ||
| } | ||
| }, | ||
| "pages": { | ||
| "settings": { | ||
| "general": { | ||
| "title": "General", | ||
| "description": "Manage settings for your language and email display preferences.", | ||
| "language": "Language", | ||
| "timezone": "Timezone", | ||
| "dynamicContent": "Dynamic Content", | ||
| "dynamicContentDescription": "Allow emails to display dynamic content.", | ||
| "externalImages": "Display External Images", | ||
| "externalImagesDescription": "Allow emails to display images from external sources." | ||
| }, | ||
| "connections": { | ||
| "title": "Email Connections", | ||
| "description": "Connect your email accounts to Zero.", | ||
| "disconnectTitle": "Disconnect Email Account", | ||
| "disconnectDescription": "Are you sure you want to disconnect this email?", | ||
| "cancel": "Cancel", | ||
| "remove": "Remove", | ||
| "disconnectSuccess": "Account disconnected successfully", | ||
| "disconnectError": "Failed to disconnect account", | ||
| "addEmail": "Add Email", | ||
| "connectEmail": "Connect Email", | ||
| "connectEmailDescription": "Select an email provider to connect", | ||
| "moreComingSoon": "More coming soon" | ||
| }, | ||
| "security": { | ||
| "title": "Security", | ||
| "description": "Manage your security preferences and account protection.", | ||
| "twoFactorAuth": "Two-Factor Authentication", | ||
| "twoFactorAuthDescription": "Add an extra layer of security to your account", | ||
| "loginNotifications": "Login Notifications", | ||
| "loginNotificationsDescription": "Get notified about new login attempts", | ||
| "deleteAccount": "Delete Account" | ||
| }, | ||
| "appearance": { | ||
| "title": "Appearance", | ||
| "description": "Customize colors, fonts and view options.", | ||
| "theme": "Theme", | ||
| "inboxType": "Inbox Type" | ||
| }, | ||
| "shortcuts": { | ||
| "title": "Keyboard Shortcuts", | ||
| "description": "View and customize keyboard shortcuts for quick actions.", | ||
| "actions": { | ||
| "newEmail": "New Email", | ||
| "sendEmail": "Send Email", | ||
| "reply": "Reply", | ||
| "replyAll": "Reply All", | ||
| "forward": "Forward", | ||
| "drafts": "Drafts", | ||
| "inbox": "Inbox", | ||
| "sentMail": "Sent Mail", | ||
| "delete": "Delete", | ||
| "search": "Search", | ||
| "markAsUnread": "Mark as Unread", | ||
| "muteThread": "Mute Thread", | ||
| "printEmail": "Print Email", | ||
| "archiveEmail": "Archive Email", | ||
| "markAsSpam": "Mark as Spam", | ||
| "moveToFolder": "Move to Folder", | ||
| "undoLastAction": "Undo Last Action", | ||
| "viewEmailDetails": "View Email Details", | ||
| "goToDrafts": "Go to Drafts", | ||
| "expandEmailView": "Expand Email View", | ||
| "helpWithShortcuts": "Help with shortcuts" | ||
| } | ||
| } | ||
| }, | ||
| "createEmail": { | ||
| "body": "Body", | ||
| "example": "zero@0.email", | ||
| "attachments": "Attachments", | ||
| "dropFilesToAttach": "Drop files to attach", | ||
| "writeYourMessageHere": "Write your message here..." | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Verify coverage of all UI text.
This appears to be a comprehensive translation file, but it would be good to verify that all visible text in the application is covered by these translation keys. Consider running a verification to check for any hardcoded strings that still need to be internationalized.
🏁 Script executed:
#!/bin/bash
# Look for potential hardcoded strings in components
# This searches for JSX text content that might not be using the translation function
# Look for text within JSX tags that doesn't use translation
rg "<[^>]*>([^<{}]*[a-zA-Z]{3,}[^<{}]*)</[^>]*>" --glob "*.tsx" | grep -v "t("
# Look for string literals in UI components that might need translation
rg "label=\"[^\"]+\"" --glob "*.tsx" | grep -v "t("
rg "placeholder=\"[^\"]+\"" --glob "*.tsx" | grep -v "t("
rg "title=\"[^\"]+\"" --glob "*.tsx" | grep -v "t("Length of output: 18902
Action Required: Audit Hardcoded UI Strings for Internationalization
The automated search has revealed several instances of hardcoded text in various UI components that aren't wrapped in our translation function. While the translation file in apps/mail/messages/en.json is comprehensive, many visible texts remain unlocalized. Please review the following key areas and update them to use translation keys where user-facing text is involved:
-
Error & Status Messages:
apps/mail/app/error.tsx– "Something went wrong!" and "See the console for more information."apps/mail/app/(error)/not-found.tsx– "Page Not Found"
-
Authentication & Account Setup:
apps/mail/app/(auth)/signup/page.tsx– "Create your 0.email account"apps/mail/app/(auth)/signup/verify/page.tsx– "Verify your email"apps/mail/app/(auth)/login/page.tsx– "Welcome to 0"
-
Navigation and Sidebar:
apps/mail/components/ui/sidebar.tsx– Labels like "Toggle Sidebar" appear both as visible text and in aria attributes.
-
Miscellaneous UI Components:
- Text in components such as
envelop.tsx,command.tsx,home/hero.tsx, and others (e.g., various button labels, placeholders, titles, and descriptive texts in forms and dialogs).
- Text in components such as
Next Steps:
- Verify if each hardcoded string is intended as user-visible content.
- For those that are user-facing, refactor the code to use the internationalization function (e.g.,
t("...")) and add the corresponding key/value pair in the translation file if not already present.
This will help ensure consistency and full localization coverage across the application.
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
🛑 Comments failed to post (28)
apps/mail/messages/en.json (1)
45-51:
⚠️ Potential issueDuplicate navigation user entries
These
navUserentries duplicate what's already defined on lines 66-72. This creates potential maintenance issues if translations need to be updated in the future.To avoid duplication, consider removing this redundant section or refactoring to use a single source of truth for these translations.
- "navUser": { - "customerSupport": "Customer Support", - "documentation": "Documentation", - "appTheme": "App Theme", - "accounts": "Accounts", - "signIn": "Sign in" - }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.{ ... "searchBar": { "pickDateRange": "Pick a date or a range", "search": "Search", "clearSearch": "Clear search", "advancedSearch": "Advanced search", "quickFilters": "Quick filters" }, - "navUser": { - "customerSupport": "Customer Support", - "documentation": "Documentation", - "appTheme": "App Theme", - "accounts": "Accounts", - "signIn": "Sign in" - }, ... // The remaining code continues here. The duplicate "navUser" block defined on lines 66-72 remains as the single source of truth. }apps/mail/app/(auth)/zero/login/page.tsx (5)
87-98: 🛠️ Refactor suggestion
Internationalize login button and signup link text.
Complete the internationalization for the form button and signup link.
<Button type="submit" className="w-full"> - Login + {t("common.actions.login")} </Button> <div className="mt-6 text-center text-sm"> <p className="text-muted-foreground"> - Don't have an account?{" "} + {t("pages.login.noAccount")}{" "} <Link href="/zero/signup" className="text-white underline hover:text-white/80"> - Sign up + {t("common.actions.signup")} </Link> </p> </div>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.<Button type="submit" className="w-full"> {t("common.actions.login")} </Button> <div className="mt-6 text-center text-sm"> <p className="text-muted-foreground"> {t("pages.login.noAccount")}{" "} <Link href="/zero/signup" className="text-white underline hover:text-white/80"> {t("common.actions.signup")} </Link> </p> </div>
43-61: 🛠️ Refactor suggestion
Update form field labels and placeholders with translations.
Continue with the internationalization of form field elements.
<Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-3"> <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> - <FormLabel className="text-muted-foreground">Email</FormLabel> + <FormLabel className="text-muted-foreground">{t("common.fields.email")}</FormLabel> <FormControl> <Input - placeholder="nizzy@0.email" + placeholder={t("pages.login.emailPlaceholder")} {...field} className="bg-black text-sm text-white placeholder:text-sm" /> </FormControl> </FormItem> )} />📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.<Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-3"> <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel className="text-muted-foreground">{t("common.fields.email")}</FormLabel> <FormControl> <Input placeholder={t("pages.login.emailPlaceholder")} {...field} className="bg-black text-sm text-white placeholder:text-sm" /> </FormControl> </FormItem> )} />
104-118: 🛠️ Refactor suggestion
Internationalize footer links.
Add translations for the footer links to Terms of Service and Privacy Policy.
<footer className="absolute bottom-0 w-full py-4 px-6"> <div className="max-w-6xl mx-auto flex justify-center items-center gap-6"> <a href="/terms" className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors" > - Terms of Service + {t("common.footer.terms")} </a> <a href="/privacy" className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors" > - Privacy Policy + {t("common.footer.privacy")} </a> </div> </footer>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.<footer className="absolute bottom-0 w-full py-4 px-6"> <div className="max-w-6xl mx-auto flex justify-center items-center gap-6"> <a href="/terms" className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors" > {t("common.footer.terms")} </a> <a href="/privacy" className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors" > {t("common.footer.privacy")} </a> </div> </footer>
62-86: 🛠️ Refactor suggestion
Internationalize password field and forgot password link.
Add translations for the password section of the form.
<FormField control={form.control} name="password" render={({ field }) => ( <FormItem> <div className="flex items-center justify-between"> - <FormLabel className="text-muted-foreground">Password</FormLabel> + <FormLabel className="text-muted-foreground">{t("common.fields.password")}</FormLabel> <Link href="/forgot-password" className="text-muted-foreground text-xs hover:text-white" > - Forgot your password? + {t("pages.login.forgotPassword")} </Link> </div> <FormControl> <Input type="password" - placeholder="••••••••" + placeholder={t("common.fields.passwordPlaceholder")} {...field} className="bg-black text-white" /> </FormControl> </FormItem> )} />📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.<FormField control={form.control} name="password" render={({ field }) => ( <FormItem> <div className="flex items-center justify-between"> <FormLabel className="text-muted-foreground">{t("common.fields.password")}</FormLabel> <Link href="/forgot-password" className="text-muted-foreground text-xs hover:text-white" > {t("pages.login.forgotPassword")} </Link> </div> <FormControl> <Input type="password" placeholder={t("common.fields.passwordPlaceholder")} {...field} className="bg-black text-white" /> </FormControl> </FormItem> )} />
1-41: 🛠️ Refactor suggestion
Add internationalization support for text strings.
This file is part of a PR that adds i18n support, but the component text is hardcoded. Consider using the
useTranslationshook fromnext-intlfor text elements like headings, labels, and placeholder text.+ import { useTranslations } from "next-intl"; export default function LoginZero() { + const t = useTranslations(); const form = useForm<z.infer<typeof formSchema>>({ // ... }); // ... return ( <div className="flex h-full min-h-screen w-full items-center justify-center bg-black"> <div className="w-full max-w-md px-6 py-8 animate-in slide-in-from-bottom-4 duration-500"> <div className="mb-4 text-center"> - <h1 className="mb-2 text-4xl font-bold text-white">Login with Zero</h1> - <p className="text-muted-foreground">Enter your Zero email below to login to your account</p> + <h1 className="mb-2 text-4xl font-bold text-white">{t("pages.login.title")}</h1> + <p className="text-muted-foreground">{t("pages.login.description")}</p> </div>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements."use client"; import { Form, FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form"; import { zodResolver } from "@hookform/resolvers/zod"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import Link from "next/link"; import { z } from "zod"; import { useTranslations } from "next-intl"; const formSchema = z.object({ email: z.string().email({ message: "Please enter a valid email address" }), password: z.string().min(6, { message: "Password must be at least 6 characters" }), }); export default function LoginZero() { const t = useTranslations(); const form = useForm<z.infer<typeof formSchema>>({ resolver: zodResolver(formSchema), defaultValues: { email: "", password: "", }, }); function onSubmit(values: z.infer<typeof formSchema>) { // Use the correct sonner toast API toast.success(`Trying to log in with ${values.email}`, { description: "Login attempt", }); // Here you would typically handle authentication } return ( <div className="flex h-full min-h-screen w-full items-center justify-center bg-black"> <div className="w-full max-w-md px-6 py-8 animate-in slide-in-from-bottom-4 duration-500"> <div className="mb-4 text-center"> <h1 className="mb-2 text-4xl font-bold text-white">{t("pages.login.title")}</h1> <p className="text-muted-foreground">{t("pages.login.description")}</p> </div> {/* Remaining component JSX */} </div> </div> ); }apps/mail/components/theme/theme-toggle.tsx (2)
1-18: 🛠️ Refactor suggestion
Implement internationalization for the toggle theme text.
This component should use the
useTranslationshook for the toggle theme label to align with the PR's goal of adding i18n support."use client"; import { useTheme } from "next-themes"; import { useEffect, useState } from "react"; import { MoonIcon } from "@/components/icons/animated/moon"; import { SunIcon } from "@/components/icons/animated/sun"; + import { useTranslations } from "next-intl"; interface ThemeToggleProps { className?: string; showLabel?: boolean; } export function ThemeToggle({ className = "", showLabel = false }: ThemeToggleProps) { const [isRendered, setIsRendered] = useState(false); const { theme, resolvedTheme, setTheme } = useTheme(); + const t = useTranslations(); useEffect(() => setIsRendered(true), []);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements."use client"; import { useTheme } from "next-themes"; import { useEffect, useState } from "react"; import { MoonIcon } from "@/components/icons/animated/moon"; import { SunIcon } from "@/components/icons/animated/sun"; import { useTranslations } from "next-intl"; interface ThemeToggleProps { className?: string; showLabel?: boolean; } export function ThemeToggle({ className = "", showLabel = false }: ThemeToggleProps) { const [isRendered, setIsRendered] = useState(false); const { theme, resolvedTheme, setTheme } = useTheme(); const t = useTranslations(); useEffect(() => setIsRendered(true), []);
36-46: 🛠️ Refactor suggestion
Update the toggle theme button label with translation.
Replace the hardcoded label with a translated text.
if (!isRendered) return null; return ( <button onClick={handleThemeToggle} className={`flex items-center rounded-md p-2 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white transition-all ${className}`} - aria-label="Toggle theme" + aria-label={t("common.theme.toggle")} > {theme === "dark" ? <MoonIcon className="opacity-60" /> : <SunIcon className="opacity-60" />} - {showLabel && <span className="ml-2 text-sm">Toggle theme</span>} + {showLabel && <span className="ml-2 text-sm">{t("common.theme.toggle")}</span>} </button> ); }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.if (!isRendered) return null; return ( <button onClick={handleThemeToggle} className={`flex items-center rounded-md p-2 text-gray-600 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white transition-all ${className}`} aria-label={t("common.theme.toggle")} > {theme === "dark" ? <MoonIcon className="opacity-60" /> : <SunIcon className="opacity-60" />} {showLabel && <span className="ml-2 text-sm">{t("common.theme.toggle")}</span>} </button> );apps/mail/components/create/editor-buttons.tsx (7)
179-183:
⚠️ Potential issueFix empty EditorProvider implementation.
The default export contains an empty EditorProvider with no children or editor configuration.
export default () => { return ( - <EditorProvider slotBefore={<MenuBar />}></EditorProvider> + <EditorProvider + slotBefore={<MenuBar />} + extensions={[/* Add required extensions here */]} + > + {/* Add editor content or children here */} + </EditorProvider> ) }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.export default () => { return ( <EditorProvider slotBefore={<MenuBar />} extensions={[/* Add required extensions here */]} > {/* Add editor content or children here */} </EditorProvider> ) }
1-3: 🛠️ Refactor suggestion
Add internationalization support for editor buttons.
This component should use translations for button labels to align with the PR's internationalization goals.
import { EditorProvider, useCurrentEditor } from '@tiptap/react' import React from 'react' + import { useTranslations } from 'next-intl'📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.import { EditorProvider, useCurrentEditor } from '@tiptap/react' import React from 'react' import { useTranslations } from 'next-intl'
11-27: 🛠️ Refactor suggestion
Replace hardcoded button labels with translations.
Start updating button labels with translation keys.
return ( <div className="control-group"> <div className="button-group"> <button onClick={() => editor.chain().focus().toggleBold().run()} disabled={ !editor.can() .chain() .focus() .toggleBold() .run() } className={editor.isActive('bold') ? 'is-active' : ''} > - Bold + {t("editor.formatting.bold")} </button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.return ( <div className="control-group"> <div className="button-group"> <button onClick={() => editor.chain().focus().toggleBold().run()} disabled={ !editor.can() .chain() .focus() .toggleBold() .run() } className={editor.isActive('bold') ? 'is-active' : ''} > {t("editor.formatting.bold")} </button> <button
27-65: 🛠️ Refactor suggestion
Continue replacing button labels with translations.
Update more button labels with translation keys.
<button onClick={() => editor.chain().focus().toggleItalic().run()} disabled={ !editor.can() .chain() .focus() .toggleItalic() .run() } className={editor.isActive('italic') ? 'is-active' : ''} > - Italic + {t("editor.formatting.italic")} </button> <button onClick={() => editor.chain().focus().toggleStrike().run()} disabled={ !editor.can() .chain() .focus() .toggleStrike() .run() } className={editor.isActive('strike') ? 'is-active' : ''} > - Strike + {t("editor.formatting.strike")} </button> <button onClick={() => editor.chain().focus().toggleCode().run()} disabled={ !editor.can() .chain() .focus() .toggleCode() .run() } className={editor.isActive('code') ? 'is-active' : ''} > - Code + {t("editor.formatting.code")} </button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.<button onClick={() => editor.chain().focus().toggleItalic().run()} disabled={ !editor.can() .chain() .focus() .toggleItalic() .run() } className={editor.isActive('italic') ? 'is-active' : ''} > {t("editor.formatting.italic")} </button> <button onClick={() => editor.chain().focus().toggleStrike().run()} disabled={ !editor.can() .chain() .focus() .toggleStrike() .run() } className={editor.isActive('strike') ? 'is-active' : ''} > {t("editor.formatting.strike")} </button> <button onClick={() => editor.chain().focus().toggleCode().run()} disabled={ !editor.can() .chain() .focus() .toggleCode() .run() } className={editor.isActive('code') ? 'is-active' : ''} > {t("editor.formatting.code")} </button>
4-10: 🛠️ Refactor suggestion
Update MenuBar component to use translations.
Add translation functionality to the MenuBar component.
const MenuBar = () => { const { editor } = useCurrentEditor() + const t = useTranslations() if (!editor) { return null }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.const MenuBar = () => { const { editor } = useCurrentEditor() const t = useTranslations() if (!editor) { return null }
66-77: 🛠️ Refactor suggestion
Update remaining button labels with translations.
Continue replacing hardcoded text with translation keys.
<button onClick={() => editor.chain().focus().unsetAllMarks().run()}> - Clear marks + {t("editor.actions.clearMarks")} </button> <button onClick={() => editor.chain().focus().clearNodes().run()}> - Clear nodes + {t("editor.actions.clearNodes")} </button> <button onClick={() => editor.chain().focus().setParagraph().run()} className={editor.isActive('paragraph') ? 'is-active' : ''} > - Paragraph + {t("editor.blocks.paragraph")} </button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.<button onClick={() => editor.chain().focus().unsetAllMarks().run()}> {t("editor.actions.clearMarks")} </button> <button onClick={() => editor.chain().focus().clearNodes().run()}> {t("editor.actions.clearNodes")} </button> <button onClick={() => editor.chain().focus().setParagraph().run()} className={editor.isActive('paragraph') ? 'is-active' : ''} > {t("editor.blocks.paragraph")} </button>
77-174: 🛠️ Refactor suggestion
Continue replacing remaining text with translations.
Complete the internationalization of button labels.
Implementing all translations would be lengthy, but here's a pattern to follow for the remaining buttons:
<button onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''} > - H1 + {t("editor.blocks.h1")} </button> // Continue the same pattern for all remaining buttons // ... <button onClick={() => editor.chain().focus().setColor('#958DF1').run()} className={editor.isActive('textStyle', { color: '#958DF1' }) ? 'is-active' : ''} > - Purple + {t("editor.colors.purple")} </button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.</button> <button onClick={() => editor.chain().focus().toggleHeading({ level: 1 }).run()} className={editor.isActive('heading', { level: 1 }) ? 'is-active' : ''} > {t("editor.blocks.h1")} </button> <button onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} className={editor.isActive('heading', { level: 2 }) ? 'is-active' : ''} > H2 </button> <button onClick={() => editor.chain().focus().toggleHeading({ level: 3 }).run()} className={editor.isActive('heading', { level: 3 }) ? 'is-active' : ''} > H3 </button> <button onClick={() => editor.chain().focus().toggleHeading({ level: 4 }).run()} className={editor.isActive('heading', { level: 4 }) ? 'is-active' : ''} > H4 </button> <button onClick={() => editor.chain().focus().toggleHeading({ level: 5 }).run()} className={editor.isActive('heading', { level: 5 }) ? 'is-active' : ''} > H5 </button> <button onClick={() => editor.chain().focus().toggleHeading({ level: 6 }).run()} className={editor.isActive('heading', { level: 6 }) ? 'is-active' : ''} > H6 </button> <button onClick={() => editor.chain().focus().toggleBulletList().run()} className={editor.isActive('bulletList') ? 'is-active' : ''} > Bullet list </button> <button onClick={() => editor.chain().focus().toggleOrderedList().run()} className={editor.isActive('orderedList') ? 'is-active' : ''} > Ordered list </button> <button onClick={() => editor.chain().focus().toggleCodeBlock().run()} className={editor.isActive('codeBlock') ? 'is-active' : ''} > Code block </button> <button onClick={() => editor.chain().focus().toggleBlockquote().run()} className={editor.isActive('blockquote') ? 'is-active' : ''} > Blockquote </button> <button onClick={() => editor.chain().focus().setHorizontalRule().run()}> Horizontal rule </button> <button onClick={() => editor.chain().focus().setHardBreak().run()}> Hard break </button> <button onClick={() => editor.chain().focus().undo().run()} disabled={ !editor.can() .chain() .focus() .undo() .run() } > Undo </button> <button onClick={() => editor.chain().focus().redo().run()} disabled={ !editor.can() .chain() .focus() .redo() .run() } > Redo </button> <button onClick={() => editor.chain().focus().setColor('#958DF1').run()} className={editor.isActive('textStyle', { color: '#958DF1' }) ? 'is-active' : ''} > {t("editor.colors.purple")} </button> </div>apps/mail/app/(auth)/zero/signup/page.tsx (8)
39-45: 🛠️ Refactor suggestion
Add internationalization to heading and description text.
Replace hardcoded text with translation keys.
return ( <div className="flex h-full min-h-screen w-full items-center justify-center bg-black"> <div className="w-full max-w-md px-6 py-8 animate-in slide-in-from-bottom-4 duration-500"> <div className="mb-4 text-center"> - <h1 className="mb-2 text-4xl font-bold text-white">Signup with Zero</h1> - <p className="text-muted-foreground">Enter your email below to signup to your account</p> + <h1 className="mb-2 text-4xl font-bold text-white">{t("pages.signup.title")}</h1> + <p className="text-muted-foreground">{t("pages.signup.description")}</p> </div>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.return ( <div className="flex h-full min-h-screen w-full items-center justify-center bg-black"> <div className="w-full max-w-md px-6 py-8 animate-in slide-in-from-bottom-4 duration-500"> <div className="mb-4 text-center"> <h1 className="mb-2 text-4xl font-bold text-white">{t("pages.signup.title")}</h1> <p className="text-muted-foreground">{t("pages.signup.description")}</p> </div>
123-136: 🛠️ Refactor suggestion
Internationalize footer links.
Add translations for the Terms of Service and Privacy Policy links.
<footer className="absolute bottom-0 w-full py-4 px-6"> <div className="max-w-6xl mx-auto flex justify-center items-center gap-6"> <a href="/terms" className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors" > - Terms of Service + {t("common.footer.terms")} </a> <a href="/privacy" className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors" > - Privacy Policy + {t("common.footer.privacy")} </a> </div> </footer>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.<footer className="absolute bottom-0 w-full py-4 px-6"> <div className="max-w-6xl mx-auto flex justify-center items-center gap-6"> <a href="/terms" className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors" > {t("common.footer.terms")} </a> <a href="/privacy" className="text-[10px] text-gray-500 hover:text-gray-300 transition-colors" > {t("common.footer.privacy")} </a> </div> </footer>
49-64: 🛠️ Refactor suggestion
Internationalize form field labels and placeholders.
Update the name field with translations.
<FormItem> - <FormLabel className="text-muted-foreground">Name</FormLabel> + <FormLabel className="text-muted-foreground">{t("common.fields.name")}</FormLabel> <FormControl> <Input - placeholder="Luke" + placeholder={t("pages.signup.namePlaceholder")} {...field} className="bg-black text-sm text-white placeholder:text-sm" /> </FormControl> </FormItem>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.<FormField control={form.control} name="name" render={({ field }) => ( <FormItem> <FormLabel className="text-muted-foreground">{t("common.fields.name")}</FormLabel> <FormControl> <Input placeholder={t("pages.signup.namePlaceholder")} {...field} className="bg-black text-sm text-white placeholder:text-sm" /> </FormControl> </FormItem> )} />
85-105: 🛠️ Refactor suggestion
Internationalize password field.
Update the password field with translations.
<FormItem> <div className="flex items-center justify-between"> - <FormLabel className="text-muted-foreground">Password</FormLabel> + <FormLabel className="text-muted-foreground">{t("common.fields.password")}</FormLabel> </div> <FormControl> <Input type="password" - placeholder="••••••••" + placeholder={t("common.fields.passwordPlaceholder")} {...field} className="bg-black text-white" /> </FormControl> </FormItem>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.<FormField control={form.control} name="password" render={({ field }) => ( <FormItem> <div className="flex items-center justify-between"> <FormLabel className="text-muted-foreground">{t("common.fields.password")}</FormLabel> </div> <FormControl> <Input type="password" placeholder={t("common.fields.passwordPlaceholder")} {...field} className="bg-black text-white" /> </FormControl> </FormItem> )} />
106-118: 🛠️ Refactor suggestion
Fix contradictory text and add internationalization for button and link.
The text incorrectly says "Don't have an account?" but then provides a login link. Fix this and add translations.
<Button type="submit" className="w-full"> - Signup + {t("common.actions.signup")} </Button> <div className="mt-6 text-center text-sm"> <p className="text-muted-foreground"> - Don't have an account?{" "} + {t("pages.signup.alreadyHaveAccount")}{" "} <Link href="/zero/login" className="text-white underline hover:text-white/80"> - Login + {t("common.actions.login")} </Link> </p> </div>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.<Button type="submit" className="w-full"> {t("common.actions.signup")} </Button> <div className="mt-6 text-center text-sm"> <p className="text-muted-foreground"> {t("pages.signup.alreadyHaveAccount")}{" "} <Link href="/zero/login" className="text-white underline hover:text-white/80"> {t("common.actions.login")} </Link> </p> </div>
65-84: 🛠️ Refactor suggestion
Internationalize email field and domain suffix.
Update the email field with translations.
<FormItem> - <FormLabel className="text-muted-foreground">Email</FormLabel> + <FormLabel className="text-muted-foreground">{t("common.fields.email")}</FormLabel> <FormControl> <div className="relative w-full rounded-md"> <Input - placeholder="adam" + placeholder={t("pages.signup.emailPlaceholder")} {...field} className="bg-black text-sm text-white placeholder:text-sm w-full pr-16" /> <span className="absolute bg-popover px-3 py-2 right-0 top-0 bottom-0 flex items-center text-sm rounded-r-md text-muted-foreground border border-l-0 border-input">@0.email</span> </div> </FormControl> </FormItem>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.<FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel className="text-muted-foreground">{t("common.fields.email")}</FormLabel> <FormControl> <div className="relative w-full rounded-md"> <Input placeholder={t("pages.signup.emailPlaceholder")} {...field} className="bg-black text-sm text-white placeholder:text-sm w-full pr-16" /> <span className="absolute bg-popover px-3 py-2 right-0 top-0 bottom-0 flex items-center text-sm rounded-r-md text-muted-foreground border border-l-0 border-input">@0.email</span> </div> </FormControl> </FormItem> )} />
26-37: 🛠️ Refactor suggestion
Update onSubmit function and fix invalid text in signup message.
The signup text incorrectly says "Don't have an account?" but links to login. Also, add internationalization for toast messages.
function onSubmit(values: z.infer<typeof formSchema>) { // Append the @0.email suffix to the username const fullEmail = `${values.email}@0.email`; // Use the correct sonner toast API toast.success(`Trying to signup with ${fullEmail}`, { - description: "Signup attempt", + description: t("pages.signup.toasts.attemptDescription"), }); // Here you would typically handle authentication with the full email }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.function onSubmit(values: z.infer<typeof formSchema>) { // Append the @0.email suffix to the username const fullEmail = `${values.email}@0.email`; // Use the correct sonner toast API toast.success(`Trying to signup with ${fullEmail}`, { description: t("pages.signup.toasts.attemptDescription"), }); // Here you would typically handle authentication with the full email }
12-16: 🛠️ Refactor suggestion
Update email validation and add appropriate internationalization.
The email field validation only checks for minimum length, not format. Also, all schema error messages should be internationalized.
+ import { useTranslations } from "next-intl"; const formSchema = z.object({ - name: z.string().min(1, { message: "Name must be at least 1 character" }), - email: z.string().min(1, { message: "Username must be at least 1 character" }), - password: z.string().min(6, { message: "Password must be at least 6 characters" }), + name: z.string().min(1, { message: "pages.signup.errors.nameRequired" }), + email: z.string() + .min(1, { message: "pages.signup.errors.usernameRequired" }) + .refine(email => !email.includes('@'), { message: "pages.signup.errors.invalidUsername" }), + password: z.string().min(6, { message: "pages.signup.errors.passwordLength" }), }); export default function SignupZero() { + const t = useTranslations();Committable suggestion skipped: line range outside the PR's diff.
apps/mail/app/(auth)/login/early-access/page.tsx (2)
193-204:
⚠️ Potential issueImplement actual verification
The current verification function accepts any 6-digit code, which isn't secure for actual user validation.
Implement proper verification that validates against a server-generated code:
function handleVerify() { if (verificationCode.length === 6) { - setVerified(true); - setVerificationError(false); - toast.success("Email verified successfully!"); - // Redirect to /mail after verification - router.push("/mail"); + // Call API to verify the code + fetch("/api/verify-code", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + email: userEmail, + code: verificationCode + }), + }) + .then((res) => { + if (res.ok) { + setVerified(true); + setVerificationError(false); + toast.success(t("pages.earlyAccess.verificationSuccess")); + router.push("/mail"); + } else { + setVerificationError(true); + toast.error(t("pages.earlyAccess.invalidCode")); + } + }) + .catch(() => { + toast.error(t("common.errors.serverError")); + }); } else { setVerificationError(true); - toast.error("Please enter a valid 6-digit code"); + toast.error(t("pages.earlyAccess.invalidCodeLength")); } }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.function handleVerify() { if (verificationCode.length === 6) { // Call API to verify the code fetch("/api/verify-code", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: userEmail, code: verificationCode }), }) .then((res) => { if (res.ok) { setVerified(true); setVerificationError(false); toast.success(t("pages.earlyAccess.verificationSuccess")); router.push("/mail"); } else { setVerificationError(true); toast.error(t("pages.earlyAccess.invalidCode")); } }) .catch(() => { toast.error(t("common.errors.serverError")); }); } else { setVerificationError(true); toast.error(t("pages.earlyAccess.invalidCodeLength")); } }
1-427: 🛠️ Refactor suggestion
Add internationalization support to Early Access page
This new file implements a complete early access login flow, but lacks integration with the PR's internationalization feature. All user-facing strings are hardcoded in English, which contradicts the primary goal of this PR.
To properly integrate with the i18n system, import the
useTranslationshook and replace hardcoded strings with translation keys:+"use client"; + +import { useTranslations } from "next-intl"; import { Form, FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form"; // other imports... function EarlyAccessContent() { + const t = useTranslations(); const router = useRouter(); // rest of component implementation... // Example of text replacement: - <h1 className="mb-2 text-4xl font-bold text-white">Claim your email</h1> - <p className="text-muted-foreground">Enter your email below to claim your account</p> + <h1 className="mb-2 text-4xl font-bold text-white">{t("pages.earlyAccess.title")}</h1> + <p className="text-muted-foreground">{t("pages.earlyAccess.subtitle")}</p>Don't forget to add the corresponding translation keys to the JSON files for all supported languages.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements."use client"; import { useTranslations } from "next-intl"; import { Form, FormControl, FormField, FormItem, FormLabel } from "@/components/ui/form"; import { InputOTP, InputOTPGroup, InputOTPSlot } from "@/components/ui/input-otp"; import { useRouter, useSearchParams } from "next/navigation"; import { AnimatePresence, motion } from "framer-motion"; import { zodResolver } from "@hookform/resolvers/zod"; import { useState, useEffect, Suspense } from "react"; import { Button } from "@/components/ui/button"; import { ArrowLeft, Check } from "lucide-react"; import { Input } from "@/components/ui/input"; import { useForm } from "react-hook-form"; import confetti from "canvas-confetti"; import { cn } from "@/lib/utils"; import Image from "next/image"; import { toast } from "sonner"; import Link from "next/link"; import { z } from "zod"; const formSchema = z.object({ name: z.string().min(1, { message: "Name must be at least 1 character" }), email: z .string() .min(1, { message: "Username is required" }) .refine((value) => !value.includes("@"), { message: "Username should not include @ symbol" }), earlyAccessEmail: z.string().email({ message: "Invalid early access email address" }), password: z.string().min(6, { message: "Password must be at least 6 characters" }), }); // Add this component to safely use useSearchParams function EarlyAccessContent() { const t = useTranslations(); const router = useRouter(); const searchParams = useSearchParams(); // Get current step from URL or default to 'claim' const currentStep = searchParams.get("step") || "claim"; const [showVerification, setShowVerification] = useState( currentStep === "verify" || currentStep === "success", ); const [verified, setVerified] = useState(currentStep === "success"); const [verificationCode, setVerificationCode] = useState(""); const [verificationError, setVerificationError] = useState(false); const [userEmail, setUserEmail] = useState(searchParams.get("email") || ""); // Update URL when step changes useEffect(() => { const params = new URLSearchParams(searchParams.toString()); // Set the appropriate step if (verified) { params.set("step", "success"); } else if (showVerification) { params.set("step", "verify"); } else { params.set("step", "claim"); } // Add email to URL if we have it if (userEmail) { params.set("email", userEmail); } const newUrl = `?${params.toString()}`; router.replace(newUrl, { scroll: false }); }, [showVerification, verified, userEmail, router, searchParams]); // Trigger confetti when verified changes to true useEffect(() => { if (verified) { const duration = 3 * 1000; const animationEnd = Date.now() + duration; const randomInRange = (min: number, max: number) => { return Math.random() * (max - min) + min; }; const interval = setInterval(() => { const timeLeft = animationEnd - Date.now(); if (timeLeft <= 0) { return clearInterval(interval); } const particleCount = 50 * (timeLeft / duration); // since particles fall down, start a bit higher than random confetti({ particleCount, startVelocity: 30, spread: 360, origin: { x: randomInRange(0.1, 0.9), y: randomInRange(0, 0.2), }, }); }, 250); return () => clearInterval(interval); } }, [verified]); const form = useForm<z.infer<typeof formSchema>>({ resolver: zodResolver(formSchema), defaultValues: { email: searchParams.get("email") || "", password: "", }, }); function onSubmit(values: z.infer<typeof formSchema>) { // Append the @0.email suffix to the username const fullEmail = `${values.email}@0.email`; setUserEmail(fullEmail); // Use the correct sonner toast API toast.success("Signup successful, please verify your email"); // Show verification screen setShowVerification(true); // URL will be updated in the useEffect } // Handle form errors with toast notifications const onError = (errors: any) => { // Define error messages mapping const errorMessageMap = [ // Email errors { field: "email", pattern: "@ symbol", message: "Zero email has to have only letters (a-z), numbers (0-9), and periods (.).", }, { field: "email", pattern: "required", message: "Username is required", }, // Password errors { field: "password", pattern: "at least", message: "Password must be at least 6 characters", }, { field: "password", pattern: "", message: "Password is required", }, // Name errors { field: "name", pattern: "", message: "Name is required", }, // Early access email errors { field: "earlyAccessEmail", pattern: "Invalid", message: "Invalid early access email address", }, { field: "earlyAccessEmail", pattern: "", message: "Early access email is required", }, ]; // Find the first matching error and show toast for (const [field, fieldError] of Object.entries(errors)) { const errorMessage = (fieldError as { message?: string })?.message || ""; // Find matching error pattern const matchedError = errorMessageMap.find( (mapping) => mapping.field === field && (mapping.pattern === "" || errorMessage.includes(mapping.pattern)), ); if (matchedError) { toast.error(matchedError.message); return; } } // Fallback for any other errors toast.error("Please fix the form errors"); }; function handleVerify() { if (verificationCode.length === 6) { setVerified(true); setVerificationError(false); toast.success("Email verified successfully!"); // Redirect to /mail after verification router.push("/mail"); // URL will be updated in the useEffect } else { setVerificationError(true); toast.error("Please enter a valid 6-digit code"); } } return ( <div className="flex h-full min-h-screen w-full items-center justify-center bg-black"> <AnimatePresence mode="wait"> {currentStep === "claim" ? ( // Claim screen (signup form) <motion.div key="signup" initial={{ opacity: 1 }} exit={{ opacity: 0, y: -20 }} className="animate-in slide-in-from-bottom-4 w-full max-w-md px-6 py-8 duration-500" > <div className="mb-4 text-center"> <h1 className="mb-2 text-4xl font-bold text-white">{t("pages.earlyAccess.title")}</h1> <p className="text-muted-foreground">{t("pages.earlyAccess.subtitle")}</p> </div> <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit, onError)} className="mx-auto space-y-3"> <FormField control={form.control} name="earlyAccessEmail" render={({ field, fieldState }) => ( <FormItem> <FormLabel className="text-muted-foreground">Early access email</FormLabel> <FormControl> <Input placeholder="luke@example.com" {...field} error={!!fieldState.error} className="bg-black text-sm text-white placeholder:text-sm" /> </FormControl> </FormItem> )} /> <FormField control={form.control} name="name" render={({ field, fieldState }) => ( <FormItem> <FormLabel className="text-muted-foreground">Name</FormLabel> <FormControl> <Input placeholder="Luke Johnson" {...field} error={!!fieldState.error} className="bg-black text-sm text-white placeholder:text-sm" /> </FormControl> </FormItem> )} /> <FormField control={form.control} name="email" render={({ field, fieldState }) => ( <FormItem> <FormLabel className="text-muted-foreground">Zero Email</FormLabel> <FormControl> <div className="relative w-full rounded-md"> <Input placeholder="Username" {...field} onChange={(e) => { // Just update the field value without showing toast errors const value = e.target.value; field.onChange(value); }} error={!!fieldState.error} className="w-full bg-black pr-16 text-sm text-white placeholder:text-sm" /> <span className="border-input absolute bottom-0 right-0 top-0 flex items-center rounded-r-md border border-l-0 px-3 py-2 text-sm text-white"> @0.email </span> </div> </FormControl> </FormItem> )} /> <FormField control={form.control} name="password" render={({ field, fieldState }) => ( <FormItem> <div className="flex items-center justify-between"> <FormLabel className="text-muted-foreground">Password</FormLabel> </div> <FormControl> <Input type="password" placeholder="••••••••" {...field} error={!!fieldState.error} className="bg-black text-white" /> </FormControl> </FormItem> )} /> <Button type="submit" className="w-full"> Create account </Button> </form> </Form> </motion.div> ) : ( // Verification screen <motion.div key="verification" initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} className="w-full max-w-md px-6 py-8" > <div className="text-center"> <h1 className="mb-2 text-4xl font-bold text-white">Verify your email</h1> <p className="text-muted-foreground">Enter the 6-digit code sent to your email</p> </div> <div className="flex flex-col items-center space-y-6"> <InputOTP maxLength={6} value={verificationCode} onChange={(value) => { setVerificationCode(value); // Clear error when user starts typing again if (verificationError) { setVerificationError(false); } }} className="justify-center gap-2" > <InputOTPGroup> <InputOTPSlot index={0} className={cn( "border-input h-12 w-12 bg-black text-lg text-white", verificationError && "ring-2 ring-red-500 ring-offset-1", )} /> <InputOTPSlot index={1} className={cn( "border-input h-12 w-12 bg-black text-lg text-white", verificationError && "ring-2 ring-red-500 ring-offset-1", )} /> <InputOTPSlot index={2} className={cn( "border-input h-12 w-12 bg-black text-lg text-white", verificationError && "ring-2 ring-red-500 ring-offset-1", )} /> <InputOTPSlot index={3} className={cn( "border-input h-12 w-12 bg-black text-lg text-white", verificationError && "ring-2 ring-red-500 ring-offset-1", )} /> <InputOTPSlot index={4} className={cn( "border-input h-12 w-12 bg-black text-lg text-white", verificationError && "ring-2 ring-red-500 ring-offset-1", )} /> <InputOTPSlot index={5} className={cn( "border-input h-12 w-12 bg-black text-lg text-white", verificationError && "ring-2 ring-red-500 ring-offset-1", )} /> </InputOTPGroup> </InputOTP> <Button onClick={handleVerify} className="w-72" disabled={verificationCode.length !== 6} > Verify Email </Button> <p className="text-muted-foreground text-center text-sm"> Didn't receive a code?{" "} <button onClick={() => toast.info("New code sent!")} className="text-primary hover:underline" > Resend </button> </p> </div> </motion.div> )} </AnimatePresence> </div> ); } // Main component with Suspense boundary export default function EarlyAccess() { return ( <Suspense fallback={ <div className="flex h-full min-h-screen w-full items-center justify-center bg-black"> <div className="text-white">Loading...</div> </div> } > <EarlyAccessContent /> </Suspense> ); }apps/mail/components/create/selectors/text-buttons.tsx (1)
17-26:
⚠️ Potential issueImplement the AI chat functionality
The function currently only logs to the console without actual implementation.
Either complete the implementation or add a TODO comment with clear requirements:
const handleChatWithAI = () => { // Get selected text const selection = editor.state.selection const selectedText = selection.empty ? '' : editor.state.doc.textBetween(selection.from, selection.to) - console.log("Chat with AI about:", selectedText) - // Implement chat with AI functionality + // TODO: Implement actual API call to AI service + // Requirements: + // 1. Send selected text to /api/ai/chat endpoint + // 2. Display response in a chat panel + // 3. Allow user to continue the conversation + console.log("Chat with AI about:", selectedText) + + // Temporary implementation until API is ready + toast.info("AI chat functionality coming soon!"); }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.const handleChatWithAI = () => { // Get selected text const selection = editor.state.selection const selectedText = selection.empty ? '' : editor.state.doc.textBetween(selection.from, selection.to) // TODO: Implement actual API call to AI service // Requirements: // 1. Send selected text to /api/ai/chat endpoint // 2. Display response in a chat panel // 3. Allow user to continue the conversation console.log("Chat with AI about:", selectedText) // Temporary implementation until API is ready toast.info("AI chat functionality coming soon!"); }apps/mail/app/(auth)/login/login-client.tsx (1)
1-333: 🛠️ Refactor suggestion
Add internationalization support to LoginClient
This new component lacks integration with the internationalization feature that is the focus of this PR. All user-facing strings are hardcoded in English.
Import and use the
useTranslationshook:"use client"; +import { useTranslations } from "next-intl"; import { GitHub, Google } from "@/components/icons/icons"; import { signIn, useSession } from "@/lib/auth-client"; // other imports... export function LoginClient({ providers, isProd }: LoginClientProps) { + const t = useTranslations(); const router = useRouter(); // rest of component... // Example replacements: - <p className="text-center font-bold text-4xl md:text-5xl">Login to Zero</p> + <p className="text-center font-bold text-4xl md:text-5xl">{t("pages.login.title")}</p> - <h3 className="ml-2 text-base font-medium text-black dark:text-white"> - Configuration Required - </h3> + <h3 className="ml-2 text-base font-medium text-black dark:text-white"> + {t("pages.login.configRequired")} + </h3> - <Button>Continue with {provider.name}</Button> + <Button>{t("pages.login.continueWith", { provider: provider.name })}</Button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements."use client"; import { useTranslations } from "next-intl"; import { GitHub, Google } from "@/components/icons/icons"; import { signIn, useSession } from "@/lib/auth-client"; import { Button } from "@/components/ui/button"; import { useRouter } from "next/navigation"; import { useEffect, ReactNode, useState } from "react"; import Image from "next/image"; import { toast } from "sonner"; import { EnvVarInfo } from "@/lib/auth-providers"; import Link from "next/link"; interface EnvVarStatus { name: string; set: boolean; source: string; defaultValue?: string; } interface Provider { id: string; name: string; enabled: boolean; required?: boolean; envVarInfo?: EnvVarInfo[]; envVarStatus: EnvVarStatus[]; isCustom?: boolean; customRedirectPath?: string; } interface LoginClientProps { providers: Provider[]; isProd: boolean; } const getProviderIcon = (providerId: string, className?: string): ReactNode => { const defaultClass = className || "w-5 h-5 mr-2"; switch (providerId) { case "google": return <Google className={defaultClass} />; case "github": return <GitHub className={defaultClass} />; case "zero": return ( <Image src="/white-icon.svg" alt="Zero" width={15} height={15} className="invert dark:invert-0 mr-2" /> ); default: return null; } }; export function LoginClient({ providers, isProd }: LoginClientProps) { const t = useTranslations(); const router = useRouter(); const { data: session, isPending } = useSession(); const [expandedProviders, setExpandedProviders] = useState<Record<string, boolean>>({}); useEffect(() => { const missingProviders = providers.filter(p => p.required && !p.enabled); if (missingProviders.length > 0 && missingProviders[0]?.id) { setExpandedProviders({ [missingProviders[0].id]: true }); } }, [providers]); const missingRequiredProviders = providers .filter(p => p.required && !p.enabled) .map(p => p.name); const missingProviders = providers .filter(p => p.required && !p.enabled && p.envVarInfo) .map(p => ({ id: p.id, name: p.name, envVarInfo: p.envVarInfo || [], envVarStatus: p.envVarStatus })); const toggleProvider = (providerId: string) => { setExpandedProviders(prev => ({ ...prev, [providerId]: !prev[providerId] })); }; useEffect(() => { if (!isPending && session?.connectionId) { router.push("/mail"); } }, [session, isPending, router]); if (isPending || (session && session.connectionId)) return null; const displayProviders = isProd ? providers.filter(p => p.enabled || p.isCustom) : providers; const hasMissingRequiredProviders = missingRequiredProviders.length > 0; const shouldShowDetailedConfig = !isProd && hasMissingRequiredProviders; const shouldShowSimplifiedMessage = isProd && hasMissingRequiredProviders; const handleProviderClick = (provider: Provider) => { if (provider.isCustom && provider.customRedirectPath) { router.push(provider.customRedirectPath); } else { toast.promise( signIn.social({ provider: provider.id as any, callbackURL: "/mail", }), { loading: "Redirecting...", success: "Redirected successfully!", error: "Login redirect failed", } ); } }; const sortedProviders = [...displayProviders].sort((a, b) => { if (a.id === "zero") return -1; if (b.id === "zero") return 1; if (a.required && !b.required) return -1; if (!a.required && b.required) return 1; return 0; }); return ( <div className="flex flex-col min-h-screen w-full items-center justify-between"> <div className="absolute top-4 left-4 z-10"> <Link href="/"> <Image src="/white-icon.svg" alt="Zero" width={40} height={40} className="cursor-pointer invert dark:invert-0" /> </Link> </div> <div className="max-w-[600px] mx-auto space-y-8 px-4 duration-500 animate-in slide-in-from-bottom-4 sm:px-12 md:px-0 flex-grow flex items-center justify-center"> <div className="space-y-8 w-full"> <p className="text-center font-bold text-4xl md:text-5xl">{t("pages.login.title")}</p> {shouldShowDetailedConfig && ( <div className="bg-black/5 border border-black/10 rounded-lg p-5 dark:bg-white/5 dark:border-white/10"> <div className="flex flex-col space-y-4"> <div className="flex items-center"> <svg className="h-5 w-5 text-black dark:text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2} > <path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> </svg> <h3 className="ml-2 text-base font-medium text-black dark:text-white"> {t("pages.login.configRequired")} </h3> </div> <p className="text-sm text-black/80 dark:text-white/80"> To enable login with <span className="font-semibold">{missingRequiredProviders.join(', ')}</span>, add these variables to your <code className="font-mono bg-black/10 dark:bg-white/10 px-1.5 py-0.5 rounded">.env</code> file: </p> <div className="space-y-3"> {missingProviders.map((provider) => ( <div key={provider.id} className="border border-black/10 dark:border-white/10 rounded-md overflow-hidden"> <button onClick={() => toggleProvider(provider.id)} className="flex w-full items-center justify-between px-4 py-3 text-left bg-black/5 dark:bg-white/5 hover:bg-black/10 dark:hover:bg-white/10 transition-colors" > <div className="flex items-center"> {getProviderIcon(provider.id)} <span className="font-medium text-black dark:text-white">{provider.name}</span> </div> <div className="flex items-center"> <span className="text-xs mr-2 text-black/60 dark:text-white/60"> {provider.envVarStatus.filter(v => !v.set).length} missing </span> <svg className={`h-5 w-5 text-black/60 dark:text-white/60 transition-transform duration-200 ${expandedProviders[provider.id] ? 'rotate-180' : ''}`} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" > <polyline points="6 9 12 15 18 9"></polyline> </svg> </div> </button> <div className={`overflow-hidden transition-all duration-300 ease-in-out ${expandedProviders[provider.id] ? 'max-h-[500px] opacity-100' : 'max-h-0 opacity-0'}`} > <div className="p-4 font-mono text-sm bg-black/[0.03] dark:bg-white/[0.03]"> {provider.envVarStatus.map((envVar) => ( <div key={envVar.name} className={`flex items-start mb-3 last:mb-0 transition-all duration-300 ease-in-out ${expandedProviders[provider.id] ? 'translate-y-0 opacity-100' : 'translate-y-2 opacity-0'}`} style={{ transitionDelay: expandedProviders[provider.id] ? `${provider.envVarStatus.indexOf(envVar) * 50}ms` : '0ms' }} > <div className={`w-2 h-2 mt-1.5 mr-2 rounded-full ${!envVar.set ? 'bg-red-500' : 'bg-green-500'}`} /> <div className="flex-1"> <div className="flex items-center"> <span className="text-black dark:text-white font-semibold">{envVar.name}</span> <span className={`ml-2 text-xs px-1.5 py-0.5 rounded ${!envVar.set ? 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300' : 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300'}`}> {!envVar.set ? 'Missing' : 'Set'} </span> </div> {!envVar.set && ( <div className="mt-1.5"> <code className="block text-black/80 dark:text-white/80"> {envVar.name}={envVar.defaultValue || `# from ${envVar.source}`} </code> </div> )} </div> </div> ))} </div> </div> </div> ))} </div> <a href="https://github.com/Mail-0/Mail-0/blob/main/README.md" target="_blank" rel="noopener noreferrer" className="text-sm inline-flex items-center text-black/60 hover:text-black dark:text-white/60 dark:hover:text-white underline underline-offset-2" > Setup instructions in documentation <svg className="ml-1 h-3 w-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" > <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" /> </svg> </a> </div> </div> )} {shouldShowSimplifiedMessage && ( <div className="bg-black/5 border border-black/10 rounded-lg p-4 dark:bg-white/5 dark:border-white/10"> <div className="flex items-center"> <svg className="h-5 w-5 text-black dark:text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2} > <path strokeLinecap="round" strokeLinejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> </svg> <p className="ml-2 text-sm text-black/80 dark:text-white/80"> Authentication service unavailable </p> </div> </div> )} {!hasMissingRequiredProviders && ( <div className="relative z-10 mx-auto flex w-full flex-col items-center justify-center gap-2"> {sortedProviders.map(provider => (provider.enabled || provider.isCustom) && ( <Button key={provider.id} onClick={() => handleProviderClick(provider)} className="border-input bg-background text-primary hover:bg-accent hover:text-accent-foreground h-12 w-full rounded-lg border-2 bg-black" > {getProviderIcon(provider.id)} {t("pages.login.continueWith", { provider: provider.name })} </Button> ) )} </div> )} </div> </div> <footer className="w-full py-4 px-6"> <div className="max-w-6xl mx-auto flex justify-center items-center gap-6"> <a href="/terms" className="text-[10px] text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200 transition-colors" > Terms of Service </a> <a href="/privacy" className="text-[10px] text-gray-500 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200 transition-colors" > Privacy Policy </a> </div> </footer> </div> ); }🧰 Tools
🪛 Biome (1.9.4)
[error] 99-99: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
apps/mail/components/create/editor.tsx (1)
115-129:
⚠️ Potential issueUnrestricted link protocol could be exploited.
Currently, a user can enter potentially dangerous protocols (e.g., "javascript:") that won't be sanitized.Consider disallowing scripts or other malicious protocols, for example:
- if (formattedUrl && !/^https?:\/\//i.test(formattedUrl)) { - formattedUrl = `https://${formattedUrl}`; - } + if (formattedUrl) { + // Drop any suspicious protocols + if (/^javascript:/i.test(formattedUrl)) { + editor.chain().focus().unsetLink().run(); + setLinkDialogOpen(false); + return; + } + // Auto-add https if missing + if (!/^https?:\/\//i.test(formattedUrl)) { + formattedUrl = `https://${formattedUrl}`; + } + }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.const handleSaveLink = () => { // empty if (linkUrl === "") { editor.chain().focus().unsetLink().run(); } else { // Format the URL with proper protocol if missing let formattedUrl = linkUrl; if (formattedUrl) { // Drop any suspicious protocols if (/^javascript:/i.test(formattedUrl)) { editor.chain().focus().unsetLink().run(); setLinkDialogOpen(false); return; } // Auto-add https if missing if (!/^https?:\/\//i.test(formattedUrl)) { formattedUrl = `https://${formattedUrl}`; } } // set link editor.chain().focus().setLink({ href: formattedUrl }).run(); } setLinkDialogOpen(false); };
There was a problem hiding this comment.
Actionable comments posted: 0
🔭 Outside diff range comments (1)
apps/mail/components/create/create-email.tsx (1)
157-175:⚠️ Potential issueRemove duplicated effect.
There's a duplicated useEffect block with identical functionality. This is unrelated to the internationalization changes but should be fixed.
React.useEffect(() => { if (!hasUnsavedChanges) return; const autoSaveTimer = setTimeout(() => { saveDraft(); }, 3000); return () => clearTimeout(autoSaveTimer); }, [hasUnsavedChanges, saveDraft]); - React.useEffect(() => { - if (!hasUnsavedChanges) return; - - const autoSaveTimer = setTimeout(() => { - saveDraft(); - }, 3000); - - return () => clearTimeout(autoSaveTimer); - }, [hasUnsavedChanges, saveDraft]);
🧹 Nitpick comments (4)
apps/mail/components/ui/nav-main.tsx (1)
217-217: Consider using nullish coalescing operator instead of logical OR.While adding a fallback value of "0" for undefined counts is a good practice, consider using the nullish coalescing operator (
??) instead of logical OR (||).The
||operator will use the fallback for any falsy value (including 0), while??only uses the fallback fornullorundefined. This ensures that legitimate zero counts are displayed as "0" from the actual data.- ?.count?.toLocaleString() || "0"} + ?.count?.toLocaleString() ?? "0"}apps/mail/components/create/create-email.tsx (3)
112-112: Consider translating error messages.There are several untranslated error messages in the component that should be internationalized for consistency.
- toast.error(`Invalid email format: ${trimmedEmail}`); + toast.error(t("errors.invalidEmailFormat", { email: trimmedEmail }));You should also consider translating other error and success messages at lines 59, 85, 151, 183, 188, 193, 205, and 214.
458-464: Internationalize draft save status messages.The draft save status messages ("Saving...", "Save draft", "Draft saved") should also be translated for consistency.
{isSaving ? ( <> - <span className="animate-pulse">Saving...</span> + <span className="animate-pulse">{t("common.drafts.saving")}</span> </> ) : hasUnsavedChanges ? ( - <>Save draft</> + <>{t("common.drafts.saveDraft")}</> ) : ( - <>Draft saved</> + <>{t("common.drafts.draftSaved")}</> )}
385-386: Consider optimizing repeated translations.The same translation key is used multiple times (here and at lines 373-374). Consider storing translated strings in variables when they're used multiple times to avoid redundant function calls.
+ const fileCountText = t("common.replyCompose.fileCount", { count: attachments.length }); + const attachmentsText = t("pages.createEmail.attachments"); // Then use fileCountText and attachmentsText variables where needed
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
apps/mail/components/create/create-email.tsx(10 hunks)apps/mail/components/ui/app-sidebar.tsx(3 hunks)apps/mail/components/ui/nav-main.tsx(3 hunks)apps/mail/package.json(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/mail/components/ui/app-sidebar.tsx
- apps/mail/package.json
🧰 Additional context used
🪛 Biome (1.9.4)
apps/mail/components/ui/nav-main.tsx
[error] 211-211: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (10)
apps/mail/components/ui/nav-main.tsx (5)
10-11: Good addition of internationalization imports.The imports for
useTranslationsandMessageKeyare correctly added to support the i18n integration.
179-180: Correctly implemented translation hook.The
useTranslationshook is properly set up to enable internationalization across the component.
188-188: Good implementation of text translation.The component now correctly uses the translation function to display internationalized text.
210-210: Good implementation of text translation.The component now correctly uses the translation function to display internationalized text.
211-217: Fix potential null reference issue with optional chaining.This is a duplicate of a previously identified issue. The static analysis tool correctly suggests improving the optional chaining pattern for better code safety.
🧰 Tools
🪛 Biome (1.9.4)
[error] 211-211: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
apps/mail/components/create/create-email.tsx (5)
14-14: Good use of next-intl for internationalization.Adding the useTranslations hook import is the first step toward internationalizing the component.
94-95: LGTM - Proper hook initialization.Good practice to initialize the translation hook at the top level of your component.
268-268: Correctly replaced hardcoded string with translation key.This implementation properly uses the translation function for the file drop message.
309-309: Properly conditionally rendered placeholder text.Good implementation that maintains the conditional logic while internationalizing the placeholder.
373-374: Good implementation of pluralization.The use of the count parameter in the translation is the correct approach for handling pluralized content.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/mail/components/mail/mail-iframe.tsx (1)
61-61: Consider translating the iframe title attributeWhile you've correctly internationalized the loading message, consider also translating the "Email Content" title attribute on the iframe for complete internationalization support.
- title="Email Content" + title={t("common.mailDisplay.emailContentTitle")}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
apps/mail/app/(error)/not-found.tsx(1 hunks)apps/mail/app/(routes)/settings/[...settings]/page.tsx(2 hunks)apps/mail/components/create/create-email.tsx(12 hunks)apps/mail/components/mail/mail-iframe.tsx(3 hunks)apps/mail/components/mail/reply-composer.tsx(12 hunks)apps/mail/messages/en.json(1 hunks)apps/mail/messages/fr.json(1 hunks)apps/mail/messages/tr.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/mail/components/mail/reply-composer.tsx
- apps/mail/components/create/create-email.tsx
🔇 Additional comments (31)
apps/mail/app/(routes)/settings/[...settings]/page.tsx (3)
9-9: Well implemented i18n import.The import for
useTranslationsfrom thenext-intllibrary is correctly added to support the internationalization requirements.
24-24: Properly initialized translation hook.The translation hook is correctly initialized and stored in the
tconstant, following best practices for using thenext-intllibrary.
29-29: Good implementation of internationalized error message.The hardcoded error message has been properly replaced with a translation key using the hierarchical structure (
pages.error.settingsNotFound), which is a good practice for organizing translations.apps/mail/app/(error)/not-found.tsx (2)
6-6: Successful implementation of internationalization!The introduction of the
useTranslationshook and replacement of hardcoded text with translation keys is well implemented. The translation keys follow a logical hierarchical structure (pages.error.notFound.title/description/goBack), making them easy to understand and maintain.Also applies to: 10-10, 25-27, 38-38
13-14: Clean CSS class reorganizationThe reordering of CSS classes (putting Tailwind utility classes after component classes) improves readability while maintaining the same styling functionality.
Also applies to: 16-16, 18-18, 35-35
apps/mail/components/mail/mail-iframe.tsx (3)
3-3: LGTM! Import for internationalization supportThis import correctly adds the necessary hook for internationalization support from the next-intl library.
16-16: LGTM! Translation hook initializationProperly initialized the translation hook that will be used to translate UI text.
50-50: LGTM! Proper internationalization implementationGood replacement of the hardcoded string with a translation key following a logical namespace hierarchy (common.mailDisplay.loadingMailContent).
apps/mail/messages/fr.json (10)
1-16: Overall Structure and Formatting – French Translations
The file is well organized, with clear categorization (common, themes, commandPalette, etc.) and correct JSON syntax. The translations for basic UI elements (e.g., actions, themes) appear accurate and consistent.
17-45: Command Palette Section – French Translations
The keys and values under the commandPalette section are clear and linguistically sound. They properly localize quick navigation and action items.
46-65: Search Bar Section – French Translations
The translations for the search bar entries (e.g., “pickDateRange”, “search”, “advancedSearch”) are appropriate and consistent with French UI terminology.
66-72: Navigation User Section – French Translations
The values for “navUser” (like “Support client” and “Se connecter”) are correctly translated and contextually appropriate.
73-79: Mail Categories and Reply/Compose Sections
Both the mailCategories and replyCompose sections use clear and context-specific language. The pluralization strings in “attachmentCount” and “fileCount” are properly formatted.
80-113: Mail Display and Thread Display Sections
The translations in these sections (e.g., “Chargement du contenu de l'email…”, “Quitter le mode plein écran”) are well done. No issues identified in formatting or terminology.
115-124: Navigation Sidebar Section
The sidebar keys are correctly mapped (e.g., “Boîte de réception”, “Brouillons”) and maintain consistency with the common nomenclature used in French interfaces.
126-142: Settings and Error Pages Sections
The “settings” and “pages” sections provide clear, descriptive text for both settings and error messages. The keys and their French values are correctly structured.
210-216: Create Email Section – French Translations
The translations for composing an email (e.g., “Corps”, “Déposez des fichiers pour les joindre”) are clear and contextually correct.
143-209:⚠️ Potential issueSettings Subsections – Detailed Translation Review
All subsections (general, connections, security, appearance, shortcuts) are detailed and comprehensive.However, in the shortcuts section (lines approximately 186–207), the translation for the key
"inbox"is given as"Emails envoyés", which is identical to"sentMail". Typically, the"inbox"key should represent the user's inbox (e.g., "Boîte de réception").Suggested Diff:
- "inbox": "Emails envoyés", + "inbox": "Boîte de réception",Please verify if this correction aligns with the intended UI context.
Likely an incorrect or invalid review comment.
apps/mail/messages/en.json (5)
1-16: Overall Structure and Formatting – English Translations
The English localization file is comprehensive and well structured, with clear sections covering common actions, themes, command palette, search bar, and navigation.
17-45: Command Palette and Actions – English Translations
All keys in the command palette (titles, descriptions, placeholders, commands) are correctly written in English with appropriate clarity and consistency.
46-65: Search Bar and Nav User Sections
The search bar and navUser translations (e.g., “Clear search”, “Customer Support”) are correctly rendered. They maintain clarity in UI communication.
66-114: Remaining Sections (Mail Categories, ReplyCompose, MailDisplay, ThreadDisplay, Sidebar)
All sections provide clear, unambiguous text strings. The hierarchical organization and key naming conventions are consistent with UI expectations.
115-221: Settings, Pages, and Create Email Sections
The translations for settings (general preferences, connections, security, appearance, shortcuts) and email creation workflows are detailed and correctly localized. The keys such as "inbox", "sentMail", and "drafts" correctly use distinct values (e.g.,"inbox": "Inbox"vs."sentMail": "Sent Mail").apps/mail/messages/tr.json (8)
1-16: Overall Structure and Formatting – Turkish Translations
The Turkish translation file mirrors the structure of the other locale files and is properly segmented (common, themes, commandPalette, etc.). The file format and JSON syntax are correct.
17-44: Command Palette and Search Bar – Turkish Translations
The commandPalette section is consistent with the localization in other languages. However, in the searchBar section, please note the upcoming comment regarding the “clearSearch” key.
50-71: Remaining Common, NavUser, and Mail Categories Sections
The translations for keys such as “search,” “recipient,” “sender,” and the navUser section (e.g., “Müşteri Desteği”, “Giriş Yap”) are contextually accurate and well phrased.
72-113: Reply/Compose, Mail Display, and Thread Display Sections – Turkish Translations
These sections maintain consistency with clear, user-friendly language (e.g., “Yanıtla”, “E-posta içeriği yükleniyor…”).
115-132: Navigation Sidebar and Settings Sections – Turkish Translations
The translations under the navigation-sidebar and settings overview (e.g., “Posta Kutusu”, “Taslaklar”, “Genel”, “Bağlantılar”) are correctly localized.
133-141: Error Pages Section – Turkish Translations
The error messages and settings-not-found strings, such as “Sayfa Bulunamadı” and “404 - Ayarlar sayfası bulunamadı”, are clear and correctly communicated.
142-209: Detailed Settings Subsections – Turkish Translations
The settings subsections (general, connections, security, appearance, shortcuts) are detailed and well translated. The “shortcuts” section, in particular, uses distinct translations for keys like"inbox": "Gelen Kutusu"and"sentMail": "Gönderilen Postalar", which is correct.
210-216: Create Email Section – Turkish Translations
The final section related to email creation (e.g., “İçerik”, “Mesajınızı buraya yazın...”) is accurate and maintains consistency with the other locale files.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
ahmetskilinc
left a comment
There was a problem hiding this comment.
implementation and Turkish language look good to me
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
apps/mail/components/mail/mail.tsx (1)
56-56: Consider using optional chaining for safer property access.The static analysis hints suggest using optional chaining for accessing the
tagsproperty. This would prevent potential errors if an item doesn't have a tags property.-const filtered = items.filter((item) => item.tags && item.tags.includes(filterTag)); +const filtered = items.filter((item) => item.tags?.includes(filterTag));🧰 Tools
🪛 Biome (1.9.4)
[error] 56-56: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
apps/mail/components/mail/thread-display.tsx (1)
274-277: Consider consolidating class namesWhile the button styling works, there's an opportunity to use the
cn()utility to organize and consolidate the class names for better maintainability.- <Button - variant="ghost" - className="h-8 w-8 p-0 md:h-fit md:w-auto md:px-2" + <Button + variant="ghost" + className={cn("h-8 w-8 p-0 md:h-fit md:w-auto md:px-2")}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (12)
apps/mail/components/connection/add.tsx(1 hunks)apps/mail/components/create/create-email.tsx(12 hunks)apps/mail/components/mail/mail-display.tsx(5 hunks)apps/mail/components/mail/mail.tsx(15 hunks)apps/mail/components/mail/reply-composer.tsx(13 hunks)apps/mail/components/mail/thread-display.tsx(6 hunks)apps/mail/components/ui/app-sidebar.tsx(3 hunks)apps/mail/components/ui/nav-main.tsx(3 hunks)apps/mail/messages/en.json(1 hunks)apps/mail/messages/fr.json(1 hunks)apps/mail/messages/tr.json(1 hunks)apps/mail/package.json(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
- apps/mail/messages/tr.json
- apps/mail/components/ui/app-sidebar.tsx
- apps/mail/package.json
- apps/mail/messages/fr.json
- apps/mail/components/mail/mail-display.tsx
- apps/mail/components/mail/reply-composer.tsx
- apps/mail/components/create/create-email.tsx
- apps/mail/components/connection/add.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
apps/mail/components/mail/mail.tsx
[error] 56-56: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
apps/mail/components/ui/nav-main.tsx
[error] 211-211: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (24)
apps/mail/components/mail/mail.tsx (9)
25-25: Good integration of the internationalization library.Adding the
useTranslationshook from 'next-intl' is a key change that enables internationalization support throughout the component.
49-54: Style refactoring of category map keys.The quotes around object keys have been removed, which is a valid JavaScript syntax when keys are valid identifiers. This change is stylistic and doesn't affect functionality.
400-433: Good implementation of i18n for mail categories.The hardcoded category names have been replaced with translation keys, which is essential for internationalization. These keys will now be used to retrieve localized strings from the translation files.
436-446: Improved type definitions in function signature.The parameter types have been formatted with semicolons instead of commas, providing better clarity in the TypeScript interface. This follows TypeScript best practices.
448-449: Added translation hook for i18n support.The
useTranslationshook is properly initialized, allowing the component to access localized strings through thetfunction.
551-553: Correctly implemented translation of category names.The category names are now properly translated using the
tfunction and the appropriate message keys, replacing hardcoded strings with localized content.
558-558: Consistent translation in tooltip content.The tooltip content is also properly internationalized, ensuring a consistent user experience across the entire component.
177-182: Improved code formatting for the useThreads hook call.The parameter formatting has been improved for better readability, which aligns with general code style best practices.
295-295: Simplified conditional rendering in MailCategoryTabs.The double negation operator (
!!) is used to convert a nullable value to a boolean, which is a concise way to handle the conditional rendering of the icons.apps/mail/components/ui/nav-main.tsx (5)
10-11: Good implementation of i18n importsThe imports for
MessageKeys,useTranslationsfromnext-intlandMessageKeyfrom the navigation config are appropriate for implementing internationalization support as outlined in the PR objectives.
179-180: Well-placed translation hook initializationThe
useTranslationshook is correctly initialized inside theNavItemcomponent to make translation functions available where they're needed.
188-188: Properly implemented translationThe text rendering change correctly implements translations by using the translation function with proper type casting, resolving the previously identified issue with an extra "s" character.
210-210: Properly implemented translationThe text rendering change correctly implements translations by using the translation function with proper type casting.
217-217: Good fallback implementation for badge countAdding the fallback value of "0" for undefined counts is a good practice to ensure that a meaningful value is always displayed, addressing the previous review suggestion.
apps/mail/messages/en.json (3)
1-222: Well-structured translations file!This is a comprehensive and well-organized English localization file. The hierarchical structure makes it easy to locate translations, and I appreciate the proper use of pluralization in the attachment and file count entries.
86-87: Good implementation of pluralizationThe pluralization format for attachments and files is well implemented, properly handling zero, singular, and plural cases.
1-222:Details
❓ Verification inconclusive
Verify all UI strings are covered
While this translation file appears comprehensive, it's important to ensure all user-facing text in the application is covered.
🏁 Script executed:
#!/bin/bash # Look for potential hardcoded strings in components that might need internationalization # Check for common UI text patterns in TSX/JSX files echo "Searching for potential hardcoded strings in component files..." rg --type tsx --type jsx -C 1 "(\"|')\w{3,50}(\"|')" -g "apps/mail/**" | grep -v "t(" # Check specifically for common UI attributes that often contain user-facing text echo "Checking for potentially untranslated UI attributes..." rg --type tsx --type jsx -C 1 "(title|placeholder|aria-label|label)=(\"|')\w{3,}(\"|')" -g "apps/mail/**" | grep -v "t("Length of output: 530
Below is an updated shell script that searches for potential hardcoded UI text in the Mail app’s JSX/TSX files without relying on ripgrep’s built‐in file type definitions. The previous script produced “unrecognized file type: tsx” errors, so this version uses explicit glob patterns to target *.tsx and *.jsx files. Please run this script and manually verify that all user-facing text is being translated (i.e. wrapped in t() or similar) so that nothing is left hardcoded.
Next Steps:
- Run the updated script to see if any untranslated user-facing text remains.
- Manually verify that all UI text in components is either translated or intentionally excluded.
Once confirmed, you can be confident that the translation file in
apps/mail/messages/en.jsonis comprehensive.
🏁 Script executed:
#!/bin/bash # Verify presence of TSX and JSX files in the Mail app echo "Listing *.tsx files in apps/mail..." fd -e tsx apps/mail echo "Listing *.jsx files in apps/mail..." fd -e jsx apps/mail # Search for potential hardcoded strings by looking for string literals, # filtering out those that appear to be wrapped in a translation function ("t("). echo "Searching for potential hardcoded UI strings in TSX/JSX files..." rg -C 1 "(\"|')\w{3,50}(\"|')" apps/mail --glob "*.tsx" --glob "*.jsx" | grep -v "t(" # Specifically check UI attributes that often contain user-facing text echo "Checking for untranslated UI attributes (title, placeholder, aria-label, label)..." rg -C 1 "(title|placeholder|aria-label|label)=(\"|')\w{3,}(\"|')" apps/mail --glob "*.tsx" --glob "*.jsx" | grep -v "t("Length of output: 66870
Action: Verify Comprehensive Internationalization of UI Text
The translation file in
apps/mail/messages/en.jsonappears to cover a wide range of UI strings. An automated scan for hardcoded strings in the JSX/TSX components—using glob patterns to bypass file-type issues—did not flag any obvious untranslated messages that are missing a translation wrapper (e.g.,t("…")). However, the output contained many matches from various parts of the code, many of which are not necessarily user-facing text (e.g., import statements, configuration strings, etc.), which makes purely automated inference inconclusive.Please verify manually:
- User-Facing Text: Ensure that any literal string displayed to the user in components is properly localized via your translation function.
- Component Review: Confirm that components with UI labels, placeholders, and other text (e.g., titles, aria-labels) are either using the translation keys provided in the JSON file or intentionally left unlocalized.
- Ongoing Sync: As the UI evolves, ensure that new text strings are added to the translation file if they are meant for end users.
Based on our initial checks, no glaring issues were found, but a final manual verification is recommended to ensure full coverage.
apps/mail/components/mail/thread-display.tsx (7)
21-26: Good integration of internationalizationThe added import for
useTranslationsfromnext-intlproperly sets up this component for internationalization.
199-200: Proper hook usage for translationsThe implementation of the
useTranslationshook is correct and follows best practices for NextJS internationalization.
242-246: Successfully replaced hardcoded strings with translationsYou've correctly replaced the hardcoded "Close" label with the translation key, maintaining the same functionality while enabling internationalization.
251-255: Good conditional translation implementationThe conditional rendering for fullscreen/exit fullscreen text is properly internationalized while maintaining the existing logic.
280-281: Successfully internationalized aria/screen reader textGreat attention to detail by ensuring the screen reader text is also properly internationalized.
284-296: Complete dropdown menu internationalizationAll dropdown menu items have been properly internationalized with appropriate translation keys.
337-343: Consistent internationalization patternsYou've maintained consistent patterns for internationalization throughout the component, including in conditional rendering scenarios.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
apps/mail/components/ui/nav-main.tsx (1)
211-218: Fixed potential null reference issue with fallback value.Good implementation of the fallback value for count using the logical OR operator, which addresses the null reference issue mentioned in the previous review.
However, the static analysis tool is suggesting an improvement to the way we're chaining operations:
{stats && stats.find((stat) => stat.label?.toLowerCase() === item.title?.toLowerCase()) && ( <Badge className="ml-auto rounded-md" variant="outline"> {stats - .find((stat) => stat.label?.toLowerCase() === item.title?.toLowerCase()) + ?.find((stat) => stat.label?.toLowerCase() === item.title?.toLowerCase()) ?.count?.toLocaleString() || "0"} </Badge> )}While your code already checks if
statsexists in the outer condition, adding the optional chaining beforefindwould follow best practices for optional chaining throughout the entire expression.🧰 Tools
🪛 Biome (1.9.4)
[error] 211-211: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/mail/app/(routes)/settings/connections/page.tsx(5 hunks)apps/mail/components/ui/nav-main.tsx(3 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
apps/mail/components/ui/nav-main.tsx
[error] 211-211: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (14)
apps/mail/components/ui/nav-main.tsx (4)
10-11: Great internationalization setup!Nicely added imports for the internationalization functionality with
next-intl. TheMessageKeytype import ensures proper type-checking for translation keys.
179-180: LGTM!Clean implementation of the translation hook.
188-188: Fixed translation string implementation.Good job removing the trailing "s" character that was previously flagged in the earlier review. The translation function is now properly implemented.
210-210: Translation properly implemented.The title is correctly passed to the translation function with the appropriate type casting.
apps/mail/app/(routes)/settings/connections/page.tsx (10)
14-14: Good implementation of i18n importsThe addition of
useTranslationsfrom next-intl and maintaining existing component imports aligns well with the internationalization objective of this PR.Also applies to: 21-21
31-31: Correct usage of the translation hookThe translation hook is properly initialized and stored in the
tvariable, following next-intl best practices.
36-36: User feedback is now properly internationalizedToast messages now use translation keys instead of hardcoded strings, which is essential for a good multilingual user experience.
Also applies to: 41-41
47-50: Settings card content properly internationalizedThe SettingsCard component now uses translated title and description, ensuring consistent internationalization across the UI.
53-53: Improved layout stylingThe layout adjustments for various elements (loading skeletons, connection items) improve the UI consistency and spacing.
Also applies to: 57-57, 61-61, 66-66, 71-71, 75-75, 131-131
138-141: Dialog content properly internationalizedThe dialog title and description now use translation keys instead of hardcoded strings, providing a consistent multilingual experience during important user actions.
145-147: Dialog buttons text properly internationalizedButton labels in the dialog are now internationalized, ensuring consistency across the UI for all interactive elements.
Also applies to: 150-152
164-165: Improved button styling and internationalized labelThe "Add Email" button has been updated with proper variant styling and its label is now internationalized, maintaining UI consistency while supporting multiple languages.
Also applies to: 170-170
89-89:Details
✅ Verification successful
Verify non-null assertion on emailProviders
The code uses a non-null assertion (
!.icon) which assumes emailProviders[0] always exists. Consider adding a fallback for safety.
🏁 Script executed:
#!/bin/bash # Check if emailProviders array is properly initialized and always has at least one element rg -A 5 "emailProviders\s*=\s*\[" --type tsLength of output: 623
Validated Non-null Assertion in Email Providers Usage
The verification confirms that theemailProvidersarray is defined inapps/mail/constants/emailProviders.tsand is initialized with at least one element (i.e., the Google provider). In this static context, the non-null assertion (!.icon) used onemailProviders[0]is safe.That said, if there’s any future chance that
emailProvidersmight become dynamic or could be empty, it would be prudent to add a fallback mechanism to prevent potential runtime errors.
21-178:Details
❓ Verification inconclusive
Verify translation key structure for consistency
The translation keys follow a nested pattern like
pages.settings.connections.disconnectSuccess. Ensure this pattern is consistently applied across the entire application and that all these keys are defined in your translation files.
🏁 Script executed:
#!/bin/bash # Check translation file structure and look for the used keys echo "Checking for translation file structure..." fd -e json "pages" --full-path "apps/mail/translations" --exec cat {} \; | jq . # Look for settings.connections keys in translation files echo "\nLooking for settings.connections keys in translations..." fd -e json -x grep -l "settings.connections" {} \; --full-path "apps/mail/translations"Length of output: 501
🏁 Script executed:
#!/bin/bash echo "Searching for translation keys in the repository..." # Search for one of the translation keys used in the connections page, e.g. "pages.settings.connections.disconnectSuccess" fd -e json -x grep -l "pages.settings.connections.disconnectSuccess" {}Length of output: 186
Verify translation key definitions and consistency
The component uses nested translation keys (e.g.,
pages.settings.connections.disconnectSuccess) that should follow a consistent pattern across the application. However, our search for these keys via automated scripts didn’t return any results. Please manually verify that:
- All keys used in this component (such as
pages.settings.connections.disconnectSuccess,pages.settings.connections.disconnectError,pages.settings.connections.title, etc.) are defined in your translation files.- The translation files (whether in a
localesdirectory, via a Next.js configuration, or another folder) follow the expected nested structure consistently.- There isn’t a misconfiguration in the file paths or directory names that might prevent the translation keys from being detected.
Once confirmed, update the translations if necessary to ensure consistency across the application.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
apps/mail/app/(routes)/settings/general/page.tsx (3)
47-47: Hardcoded default language valueThe form's default language is hardcoded as "en" rather than using the configured
defaultLocale.- language: "en", + language: defaultLocale,This would require importing
defaultLocalefrom "@/i18n/config".
78-80: Missing internationalization for toast messagesThe toast messages for signing out are not internationalized.
- loading: "Signing out...", - success: () => "Signed out successfully!", - error: "Error signing out", + loading: t("common.actions.signingOut"), + success: () => t("common.actions.signedOutSuccess"), + error: t("common.actions.signOutError"),
146-154: Non-internationalized timezone namesThe timezone display names are hardcoded in English rather than being internationalized.
Consider creating translation keys for timezone names or storing both code and translatable name in a configuration similar to
availableLocales.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/mail/app/(routes)/settings/general/page.tsx(8 hunks)apps/mail/i18n/config.ts(1 hunks)apps/mail/i18n/utils.ts(1 hunks)apps/mail/messages/es-ES.json(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- apps/mail/messages/es-ES.json
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/mail/i18n/utils.ts
🔇 Additional comments (8)
apps/mail/i18n/config.ts (3)
1-1: Clear type definition for locale supportThe
Localetype ensures type safety when working with supported languages throughout the application.
3-20: Well-structured locale configurationThe
availableLocalesarray clearly defines supported languages with both their code and human-readable name, making it easy to use in UI components like dropdown selectors.Note that Spanish uses the region-specific code "es-ES" while other languages use only the language code. This inconsistency might be intentional but could cause confusion.
Are all language codes following a consistent standard? Consider using either all base language codes (e.g., "es" instead of "es-ES") or region-specific codes for all languages if regional differences matter.
22-23: Efficient locale utilitiesThe
localesarray is derived fromavailableLocales, avoiding duplication. ThedefaultLocaleis properly typed with theLocaletype to ensure it's always a valid option.apps/mail/app/(routes)/settings/general/page.tsx (5)
21-27: Good integration of i18n dependenciesThe imports properly set up the component with the necessary internationalization utilities.
59-60: Language change implementationThe locale change functionality is correctly hooked up to the form submission.
85-86: Translation hook initializationThe
useTranslationshook is properly initialized for use throughout the component.
90-91: Heading internationalizationCard title and description successfully converted to use translation keys.
121-125: Dynamic language options from configurationGood implementation of a dynamic language selector based on the centralized locale configuration.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
apps/mail/locales/es-ES.json (1)
1-222: Review of Spanish Localization FileThe overall JSON structure is clear and well-organized with the UI elements grouped under
common,navigation, andpages, which promotes consistency across the application. Most translations are properly rendered in Spanish.One minor suggestion is to revisit the pluralization strings in the
replyComposesection (lines 86–87). Currently, the keys use English words:- "attachmentCount": "{count, plural, =0 {attachments} one {attachment} other {attachments}}", - "fileCount": "{count, plural, =0 {files} one {file} other {files}}", + "attachmentCount": "{count, plural, =0 {adjuntos} one {adjunto} other {adjuntos}}", + "fileCount": "{count, plural, =0 {archivos} one {archivo} other {archivos}}",Using Spanish terms (e.g., "adjunto/adjuntos" and "archivo/archivos") would better align with the language of this file.
apps/mail/locales/en.json (1)
1-223: English Localization File – Comprehensive and ConsistentThe English localization file covers all necessary UI elements and maintains consistency with the other language files. One point to note is the possible stray line indicator ("223") at the end of the file; please ensure that only valid JSON content is part of the final file.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/mail/global.d.ts(1 hunks)apps/mail/i18n/request.ts(1 hunks)apps/mail/locales/en.json(1 hunks)apps/mail/locales/es-ES.json(1 hunks)apps/mail/locales/fr.json(1 hunks)apps/mail/locales/tr.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/mail/global.d.ts
- apps/mail/i18n/request.ts
🔇 Additional comments (2)
apps/mail/locales/fr.json (1)
1-220: French Localization File – Looks GreatThe French translations are accurately provided, and the structure mirrors that of the other localization files. All pluralization rules (for example, in
replyCompose) are correctly localized into French with appropriate singular and plural forms.apps/mail/locales/tr.json (1)
1-220: Turkish Localization File – Solid ImplementationThe Turkish translation file is complete and well-structured, with translations for all UI elements. The pluralization (e.g., in
replyCompose) is handled appropriately, and the language usage is culturally fitting.
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
apps/mail/locales/tr.json (1)
105-118: Missing "close" key in threadDisplay section.The English file has several entries in the threadDisplay section that align with the Turkish file, but in the Turkish file there's an extra "close" key that doesn't exist in the English version. This inconsistency could cause issues.
Either remove the "close" key from the Turkish file or add it to the English file to maintain consistency:
# For the English file (en.json): "threadDisplay": { + "close": "Close", "exitFullscreen": "Exit fullscreen", "enterFullscreen": "Enter fullscreen", ...apps/mail/app/(routes)/settings/general/page.tsx (3)
59-60: Consider adding feedback for locale change.When the user changes the language, there's no feedback to indicate the change was successful. Adding a toast notification would improve the user experience.
changeLocale(values.language); + toast.success(t("pages.settings.general.languageChangedSuccess"));You'll need to add the corresponding translation key to your locale files.
117-117: Missing translation for dropdown placeholder.The SelectValue placeholder is hardcoded as "Select a language" instead of using a translation key.
- <SelectValue placeholder="Select a language" /> + <SelectValue placeholder={t("pages.settings.general.selectLanguage")} />Remember to add the "selectLanguage" key to your translation files.
142-142: Missing translation for timezone placeholder.The SelectValue placeholder for timezone is hardcoded as "Select a timezone" instead of using a translation key.
- <SelectValue placeholder="Select a timezone" /> + <SelectValue placeholder={t("pages.settings.general.selectTimezone")} />Remember to add the "selectTimezone" key to your translation files.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
apps/mail/app/(routes)/settings/general/page.tsx(9 hunks)apps/mail/locales/en.json(1 hunks)apps/mail/locales/es-ES.json(1 hunks)apps/mail/locales/fr.json(1 hunks)apps/mail/locales/tr.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- apps/mail/locales/es-ES.json
- apps/mail/locales/fr.json
🔇 Additional comments (3)
apps/mail/app/(routes)/settings/general/page.tsx (1)
121-125: Great implementation of dynamic locale selection.The dynamic population of language options from
availableLocalesis a good approach. It allows for easy addition of new languages without modifying this component.apps/mail/locales/en.json (2)
89-91: Well-implemented pluralization in translation strings.The use of ICU Message format for pluralization is excellent practice. This ensures grammatically correct translations across different languages.
"attachmentCount": "{count, plural, =0 {attachments} one {attachment} other {attachments}}", "fileCount": "{count, plural, =0 {files} one {file} other {files}}",
1-225:Details
❓ Verification inconclusive
Verify all translation files have the same structure.
Based on the comparison between English and Turkish files, there are some inconsistencies in the structure and available keys. This could lead to runtime errors or UI inconsistencies when switching languages.
Run the following script to verify all translation files have the same structure:
🏁 Script executed:
#!/bin/bash # Find all locale files locale_files=$(find apps/mail/locales -name "*.json") # Extract all keys from the English file as reference reference_keys=$(cat apps/mail/locales/en.json | jq -r 'paths | join(".")' | sort) # Check each locale file against the reference for file in $locale_files; do if [[ "$file" != *"en.json" ]]; then echo "Checking $file for missing keys..." file_keys=$(cat $file | jq -r 'paths | join(".")' | sort) # Find missing keys missing_keys=$(comm -23 <(echo "$reference_keys") <(echo "$file_keys")) if [[ -n "$missing_keys" ]]; then echo "Missing keys in $file:" echo "$missing_keys" echo "" else echo "No missing keys in $file" echo "" fi # Find extra keys extra_keys=$(comm -13 <(echo "$reference_keys") <(echo "$file_keys")) if [[ -n "$extra_keys" ]]; then echo "Extra keys in $file (not in reference):" echo "$extra_keys" echo "" fi fi doneLength of output: 69178
Translation Files Structure Verification – Manual Review Needed
The automated script shows that the Spanish and French locale files match the English structure with no missing keys. However, errors (e.g., “comm: command not found”) prevented a reliable comparison for the Turkish locale file. Please manually verify that the Turkish translation (and any other locales) share the same structure as the English file to avoid potential runtime or UI issues when switching languages.
| "mailCategories": { | ||
| "primary": "Birincil", | ||
| "important": "Önemli", | ||
| "personal": "Kişisel", | ||
| "updates": "Güncellemeler", | ||
| "promotions": "Tanıtımlar" | ||
| }, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Missing "social" category in Turkish translations.
The English translation file includes a "social" category under mailCategories, but this is missing in the Turkish translations. This inconsistency could cause UI issues when users switch languages.
Add the missing translation:
"mailCategories": {
"primary": "Birincil",
"important": "Önemli",
"personal": "Kişisel",
"updates": "Güncellemeler",
"promotions": "Tanıtımlar"
+ "social": "Sosyal"
}Committable suggestion skipped: line range outside the PR's diff.
| { | ||
| "common": { | ||
| "actions": { | ||
| "logout": "Çıkış Yap", | ||
| "back": "Geri", | ||
| "create": "Email Oluştur", | ||
| "saveChanges": "Değişiklikleri Kaydet", | ||
| "saving": "Kaydediliyor...", | ||
| "resetToDefaults": "Varsayılanlara Sıfırla", | ||
| "close": "Kapat", | ||
| "signingOut": "Çıkış yapılıyor...", | ||
| "signedOutSuccess": "Çıkış yapıldı!", | ||
| "signOutError": "Çıkış yapılırken hata oluştu" | ||
| }, | ||
| "themes": { | ||
| "dark": "Koyu", | ||
| "light": "Açık", | ||
| "system": "Sistem" | ||
| }, | ||
| "commandPalette": { | ||
| "title": "Komut Paleti", | ||
| "description": "Mail-0 için hızlı gezinme ve işlemler", | ||
| "placeholder": "Bir komut yazın veya arayın...", | ||
| "noResults": "Sonuç bulunamadı", | ||
| "groups": { | ||
| "mail": "E-posta", | ||
| "settings": "Ayarlar", | ||
| "actions": "İşlemler", | ||
| "help": "Yardım", | ||
| "navigation": "Gezinme" | ||
| }, | ||
| "commands": { | ||
| "goToInbox": "Gelen Kutusuna Git", | ||
| "goToDrafts": "Taslaklara Git", | ||
| "goToSent": "Gönderilenlere Git", | ||
| "goToSpam": "Spam'a Git", | ||
| "goToArchive": "Arşive Git", | ||
| "goToBin": "Çöp Kutusuna Git", | ||
| "goToSettings": "Ayarlara Git", | ||
| "newEmail": "Yeni E-posta", | ||
| "composeMessage": "Mesaj oluştur", | ||
| "searchEmails": "E-postaları Ara", | ||
| "toggleTheme": "Temayı Değiştir", | ||
| "backToMail": "E-postaya Geri Dön", | ||
| "goToDocs": "Dokümanlara Git", | ||
| "helpWithShortcuts": "Kısayollar Hakkında Yardım" | ||
| } | ||
| }, | ||
| "searchBar": { | ||
| "pickDateRange": "Tarih seçin veya aralık seçin", | ||
| "search": "Ara", | ||
| "clearSearch": "Aramayı temizle", | ||
| "advancedSearch": "Gelişmiş arama", | ||
| "quickFilters": "Hızlı filtreler", | ||
| "searchIn": "Ara", | ||
| "recipient": "Alıcı", | ||
| "sender": "Gönderen", | ||
| "subject": "Konu", | ||
| "dateRange": "Tarih aralığı", | ||
| "category": "Kategori", | ||
| "folder": "Klasör", | ||
| "allMail": "Tüm E-postalar", | ||
| "unread": "Okunmamış", | ||
| "hasAttachment": "Ekli", | ||
| "starred": "Yıldızlı", | ||
| "applyFilters": "Filtreleri Uygula", | ||
| "reset": "Sıfırla" | ||
| }, | ||
| "navUser": { | ||
| "customerSupport": "Müşteri Desteği", | ||
| "documentation": "Dokümantasyon", | ||
| "appTheme": "Uygulama Teması", | ||
| "accounts": "Hesaplar", | ||
| "signIn": "Giriş Yap" | ||
| }, | ||
| "mailCategories": { | ||
| "primary": "Birincil", | ||
| "important": "Önemli", | ||
| "personal": "Kişisel", | ||
| "updates": "Güncellemeler", | ||
| "promotions": "Tanıtımlar" | ||
| }, | ||
| "replyCompose": { | ||
| "replyTo": "Yanıtla", | ||
| "thisEmail": "bu e-postaya", | ||
| "dropFiles": "Eklemek için dosyaları bırakın", | ||
| "attachments": "Ekler", | ||
| "attachmentCount": "{count, plural, =0 {ekler} one {ek} other {ekler}}", | ||
| "fileCount": "{count, plural, =0 {dosyalar} one {dosya} other {dosyalar}}", | ||
| "saveDraft": "Taslağı kaydet", | ||
| "send": "Gönder" | ||
| }, | ||
| "mailDisplay": { | ||
| "details": "Detaylar", | ||
| "from": "Kimden", | ||
| "to": "Kime", | ||
| "cc": "Cc", | ||
| "date": "Tarih", | ||
| "mailedBy": "Gönderen", | ||
| "signedBy": "İmzalayan", | ||
| "security": "Güvenlik", | ||
| "standardEncryption": "Standart şifreleme (TLS)", | ||
| "loadingMailContent": "E-posta içeriği yükleniyor..." | ||
| }, | ||
| "threadDisplay": { | ||
| "close": "Kapat", | ||
| "exitFullscreen": "Tam ekrandan çık", | ||
| "enterFullscreen": "Tam ekrana geç", | ||
| "archive": "Arşivle", | ||
| "reply": "Yanıtla", | ||
| "moreOptions": "Daha fazla seçenek", | ||
| "moveToSpam": "Spam'a taşı", | ||
| "replyAll": "Tümünü yanıtla", | ||
| "forward": "İlet", | ||
| "markAsUnread": "Okunmamış olarak işaretle", | ||
| "addLabel": "Etiket ekle", | ||
| "muteThread": "Konuyu sessize al" | ||
| } | ||
| }, | ||
| "navigation": { | ||
| "sidebar": { | ||
| "inbox": "Posta Kutusu", | ||
| "drafts": "Taslaklar", | ||
| "sent": "Gönderilenler", | ||
| "spam": "Spam", | ||
| "archive": "Arşiv", | ||
| "bin": "Çöp", | ||
| "settings": "Ayarlar" | ||
| }, | ||
| "settings": { | ||
| "general": "Genel", | ||
| "connections": "Bağlantılar", | ||
| "security": "Güvenlik", | ||
| "appearance": "Görünüm", | ||
| "shortcuts": "Kısayollar" | ||
| } | ||
| }, | ||
| "pages": { | ||
| "error": { | ||
| "notFound": { | ||
| "title": "Sayfa Bulunamadı", | ||
| "description": "Üzgünüz! Aradığınız sayfa mevcut değil veya taşınmış.", | ||
| "goBack": "Geri Dön" | ||
| }, | ||
| "settingsNotFound": "404 - Ayarlar sayfası bulunamadı" | ||
| }, | ||
| "settings": { | ||
| "general": { | ||
| "title": "Genel Ayarlar", | ||
| "description": "Dil ve email görünümü ayarlarını yönetin.", | ||
| "language": "Dil", | ||
| "timezone": "Saat Dilimi", | ||
| "dynamicContent": "Dinamik İçerik", | ||
| "dynamicContentDescription": "E-postaların dinamik içerik göstermesine izin verin.", | ||
| "externalImages": "Harici Görselleri Göster", | ||
| "externalImagesDescription": "E-postaların harici kaynaklardan görselleri göstermesine izin verin." | ||
| }, | ||
| "connections": { | ||
| "title": "E-posta Bağlantıları", | ||
| "description": "E-posta hesaplarınızı Zero'ya bağlayın.", | ||
| "disconnectTitle": "E-posta Hesabını Bağlantıyı Kes", | ||
| "disconnectDescription": "Bu e-posta bağlantısını kesmek istediğinizden emin misiniz?", | ||
| "cancel": "İptal", | ||
| "remove": "Kaldır", | ||
| "disconnectSuccess": "Hesap bağlantısı başarıyla kesildi", | ||
| "disconnectError": "Hesap bağlantısı kesilirken hata oluştu", | ||
| "addEmail": "E-posta Bağlantısı Ekle", | ||
| "connectEmail": "E-posta Bağlantısı", | ||
| "connectEmailDescription": "E-posta sağlayıcısını seçin", | ||
| "moreComingSoon": "Yakında daha fazla geliyor" | ||
| }, | ||
| "security": { | ||
| "title": "Güvenlik", | ||
| "description": "Güvenlik tercihlerinizi ve hesap korumanızı yönetin.", | ||
| "twoFactorAuth": "İki Faktörlü Kimlik Doğrulama", | ||
| "twoFactorAuthDescription": "Hesabınıza ekstra bir güvenlik katmanı ekleyin", | ||
| "loginNotifications": "Giriş Bildirimleri", | ||
| "loginNotificationsDescription": "Yeni giriş denemeleri hakkında bildirim alın", | ||
| "deleteAccount": "Hesabı Sil" | ||
| }, | ||
| "appearance": { | ||
| "title": "Görünüm", | ||
| "description": "Renkleri, yazı tiplerini ve görünüm seçeneklerini özelleştirin.", | ||
| "theme": "Tema", | ||
| "inboxType": "Gelen Kutusu Tipi" | ||
| }, | ||
| "shortcuts": { | ||
| "title": "Klavye Kısayolları", | ||
| "description": "Hızlı işlemler için klavye kısayollarını görüntüleyin ve özelleştirin.", | ||
| "actions": { | ||
| "newEmail": "Yeni E-posta", | ||
| "sendEmail": "E-posta Gönder", | ||
| "reply": "Yanıtla", | ||
| "replyAll": "Tümünü Yanıtla", | ||
| "forward": "İlet", | ||
| "drafts": "Taslaklar", | ||
| "inbox": "Gelen Kutusu", | ||
| "sentMail": "Gönderilen Postalar", | ||
| "delete": "Sil", | ||
| "search": "Ara", | ||
| "markAsUnread": "Okunmadı Olarak İşaretle", | ||
| "muteThread": "Konuyu Sessize Al", | ||
| "printEmail": "E-postayı Yazdır", | ||
| "archiveEmail": "E-postayı Arşivle", | ||
| "markAsSpam": "Spam Olarak İşaretle", | ||
| "moveToFolder": "Klasöre Taşı", | ||
| "undoLastAction": "Son İşlemi Geri Al", | ||
| "viewEmailDetails": "E-posta Detaylarını Görüntüle", | ||
| "goToDrafts": "Taslaklara Git", | ||
| "expandEmailView": "E-posta Görünümünü Genişlet", | ||
| "helpWithShortcuts": "Kısayollar Hakkında Yardım" | ||
| } | ||
| } | ||
| }, | ||
| "createEmail": { | ||
| "body": "İçerik", | ||
| "example": "zero@0.email", | ||
| "attachments": "Ekler", | ||
| "dropFilesToAttach": "Eklemek için dosyaları bırakın", | ||
| "writeYourMessageHere": "Mesajınızı buraya yazın..." | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Missing email notification translations in Turkish file.
The English translation file includes keys for "emailSentSuccessfully" and "failedToSendEmail" in the createEmail section (lines 221-222), but these are missing in the Turkish translation file.
Add the missing translations:
"createEmail": {
"body": "İçerik",
"example": "zero@0.email",
"attachments": "Ekler",
"dropFilesToAttach": "Eklemek için dosyaları bırakın",
"writeYourMessageHere": "Mesajınızı buraya yazın..."
+ "emailSentSuccessfully": "E-posta başarıyla gönderildi",
+ "failedToSendEmail": "E-posta gönderilemedi. Lütfen tekrar deneyin."
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| { | |
| "common": { | |
| "actions": { | |
| "logout": "Çıkış Yap", | |
| "back": "Geri", | |
| "create": "Email Oluştur", | |
| "saveChanges": "Değişiklikleri Kaydet", | |
| "saving": "Kaydediliyor...", | |
| "resetToDefaults": "Varsayılanlara Sıfırla", | |
| "close": "Kapat", | |
| "signingOut": "Çıkış yapılıyor...", | |
| "signedOutSuccess": "Çıkış yapıldı!", | |
| "signOutError": "Çıkış yapılırken hata oluştu" | |
| }, | |
| "themes": { | |
| "dark": "Koyu", | |
| "light": "Açık", | |
| "system": "Sistem" | |
| }, | |
| "commandPalette": { | |
| "title": "Komut Paleti", | |
| "description": "Mail-0 için hızlı gezinme ve işlemler", | |
| "placeholder": "Bir komut yazın veya arayın...", | |
| "noResults": "Sonuç bulunamadı", | |
| "groups": { | |
| "mail": "E-posta", | |
| "settings": "Ayarlar", | |
| "actions": "İşlemler", | |
| "help": "Yardım", | |
| "navigation": "Gezinme" | |
| }, | |
| "commands": { | |
| "goToInbox": "Gelen Kutusuna Git", | |
| "goToDrafts": "Taslaklara Git", | |
| "goToSent": "Gönderilenlere Git", | |
| "goToSpam": "Spam'a Git", | |
| "goToArchive": "Arşive Git", | |
| "goToBin": "Çöp Kutusuna Git", | |
| "goToSettings": "Ayarlara Git", | |
| "newEmail": "Yeni E-posta", | |
| "composeMessage": "Mesaj oluştur", | |
| "searchEmails": "E-postaları Ara", | |
| "toggleTheme": "Temayı Değiştir", | |
| "backToMail": "E-postaya Geri Dön", | |
| "goToDocs": "Dokümanlara Git", | |
| "helpWithShortcuts": "Kısayollar Hakkında Yardım" | |
| } | |
| }, | |
| "searchBar": { | |
| "pickDateRange": "Tarih seçin veya aralık seçin", | |
| "search": "Ara", | |
| "clearSearch": "Aramayı temizle", | |
| "advancedSearch": "Gelişmiş arama", | |
| "quickFilters": "Hızlı filtreler", | |
| "searchIn": "Ara", | |
| "recipient": "Alıcı", | |
| "sender": "Gönderen", | |
| "subject": "Konu", | |
| "dateRange": "Tarih aralığı", | |
| "category": "Kategori", | |
| "folder": "Klasör", | |
| "allMail": "Tüm E-postalar", | |
| "unread": "Okunmamış", | |
| "hasAttachment": "Ekli", | |
| "starred": "Yıldızlı", | |
| "applyFilters": "Filtreleri Uygula", | |
| "reset": "Sıfırla" | |
| }, | |
| "navUser": { | |
| "customerSupport": "Müşteri Desteği", | |
| "documentation": "Dokümantasyon", | |
| "appTheme": "Uygulama Teması", | |
| "accounts": "Hesaplar", | |
| "signIn": "Giriş Yap" | |
| }, | |
| "mailCategories": { | |
| "primary": "Birincil", | |
| "important": "Önemli", | |
| "personal": "Kişisel", | |
| "updates": "Güncellemeler", | |
| "promotions": "Tanıtımlar" | |
| }, | |
| "replyCompose": { | |
| "replyTo": "Yanıtla", | |
| "thisEmail": "bu e-postaya", | |
| "dropFiles": "Eklemek için dosyaları bırakın", | |
| "attachments": "Ekler", | |
| "attachmentCount": "{count, plural, =0 {ekler} one {ek} other {ekler}}", | |
| "fileCount": "{count, plural, =0 {dosyalar} one {dosya} other {dosyalar}}", | |
| "saveDraft": "Taslağı kaydet", | |
| "send": "Gönder" | |
| }, | |
| "mailDisplay": { | |
| "details": "Detaylar", | |
| "from": "Kimden", | |
| "to": "Kime", | |
| "cc": "Cc", | |
| "date": "Tarih", | |
| "mailedBy": "Gönderen", | |
| "signedBy": "İmzalayan", | |
| "security": "Güvenlik", | |
| "standardEncryption": "Standart şifreleme (TLS)", | |
| "loadingMailContent": "E-posta içeriği yükleniyor..." | |
| }, | |
| "threadDisplay": { | |
| "close": "Kapat", | |
| "exitFullscreen": "Tam ekrandan çık", | |
| "enterFullscreen": "Tam ekrana geç", | |
| "archive": "Arşivle", | |
| "reply": "Yanıtla", | |
| "moreOptions": "Daha fazla seçenek", | |
| "moveToSpam": "Spam'a taşı", | |
| "replyAll": "Tümünü yanıtla", | |
| "forward": "İlet", | |
| "markAsUnread": "Okunmamış olarak işaretle", | |
| "addLabel": "Etiket ekle", | |
| "muteThread": "Konuyu sessize al" | |
| } | |
| }, | |
| "navigation": { | |
| "sidebar": { | |
| "inbox": "Posta Kutusu", | |
| "drafts": "Taslaklar", | |
| "sent": "Gönderilenler", | |
| "spam": "Spam", | |
| "archive": "Arşiv", | |
| "bin": "Çöp", | |
| "settings": "Ayarlar" | |
| }, | |
| "settings": { | |
| "general": "Genel", | |
| "connections": "Bağlantılar", | |
| "security": "Güvenlik", | |
| "appearance": "Görünüm", | |
| "shortcuts": "Kısayollar" | |
| } | |
| }, | |
| "pages": { | |
| "error": { | |
| "notFound": { | |
| "title": "Sayfa Bulunamadı", | |
| "description": "Üzgünüz! Aradığınız sayfa mevcut değil veya taşınmış.", | |
| "goBack": "Geri Dön" | |
| }, | |
| "settingsNotFound": "404 - Ayarlar sayfası bulunamadı" | |
| }, | |
| "settings": { | |
| "general": { | |
| "title": "Genel Ayarlar", | |
| "description": "Dil ve email görünümü ayarlarını yönetin.", | |
| "language": "Dil", | |
| "timezone": "Saat Dilimi", | |
| "dynamicContent": "Dinamik İçerik", | |
| "dynamicContentDescription": "E-postaların dinamik içerik göstermesine izin verin.", | |
| "externalImages": "Harici Görselleri Göster", | |
| "externalImagesDescription": "E-postaların harici kaynaklardan görselleri göstermesine izin verin." | |
| }, | |
| "connections": { | |
| "title": "E-posta Bağlantıları", | |
| "description": "E-posta hesaplarınızı Zero'ya bağlayın.", | |
| "disconnectTitle": "E-posta Hesabını Bağlantıyı Kes", | |
| "disconnectDescription": "Bu e-posta bağlantısını kesmek istediğinizden emin misiniz?", | |
| "cancel": "İptal", | |
| "remove": "Kaldır", | |
| "disconnectSuccess": "Hesap bağlantısı başarıyla kesildi", | |
| "disconnectError": "Hesap bağlantısı kesilirken hata oluştu", | |
| "addEmail": "E-posta Bağlantısı Ekle", | |
| "connectEmail": "E-posta Bağlantısı", | |
| "connectEmailDescription": "E-posta sağlayıcısını seçin", | |
| "moreComingSoon": "Yakında daha fazla geliyor" | |
| }, | |
| "security": { | |
| "title": "Güvenlik", | |
| "description": "Güvenlik tercihlerinizi ve hesap korumanızı yönetin.", | |
| "twoFactorAuth": "İki Faktörlü Kimlik Doğrulama", | |
| "twoFactorAuthDescription": "Hesabınıza ekstra bir güvenlik katmanı ekleyin", | |
| "loginNotifications": "Giriş Bildirimleri", | |
| "loginNotificationsDescription": "Yeni giriş denemeleri hakkında bildirim alın", | |
| "deleteAccount": "Hesabı Sil" | |
| }, | |
| "appearance": { | |
| "title": "Görünüm", | |
| "description": "Renkleri, yazı tiplerini ve görünüm seçeneklerini özelleştirin.", | |
| "theme": "Tema", | |
| "inboxType": "Gelen Kutusu Tipi" | |
| }, | |
| "shortcuts": { | |
| "title": "Klavye Kısayolları", | |
| "description": "Hızlı işlemler için klavye kısayollarını görüntüleyin ve özelleştirin.", | |
| "actions": { | |
| "newEmail": "Yeni E-posta", | |
| "sendEmail": "E-posta Gönder", | |
| "reply": "Yanıtla", | |
| "replyAll": "Tümünü Yanıtla", | |
| "forward": "İlet", | |
| "drafts": "Taslaklar", | |
| "inbox": "Gelen Kutusu", | |
| "sentMail": "Gönderilen Postalar", | |
| "delete": "Sil", | |
| "search": "Ara", | |
| "markAsUnread": "Okunmadı Olarak İşaretle", | |
| "muteThread": "Konuyu Sessize Al", | |
| "printEmail": "E-postayı Yazdır", | |
| "archiveEmail": "E-postayı Arşivle", | |
| "markAsSpam": "Spam Olarak İşaretle", | |
| "moveToFolder": "Klasöre Taşı", | |
| "undoLastAction": "Son İşlemi Geri Al", | |
| "viewEmailDetails": "E-posta Detaylarını Görüntüle", | |
| "goToDrafts": "Taslaklara Git", | |
| "expandEmailView": "E-posta Görünümünü Genişlet", | |
| "helpWithShortcuts": "Kısayollar Hakkında Yardım" | |
| } | |
| } | |
| }, | |
| "createEmail": { | |
| "body": "İçerik", | |
| "example": "zero@0.email", | |
| "attachments": "Ekler", | |
| "dropFilesToAttach": "Eklemek için dosyaları bırakın", | |
| "writeYourMessageHere": "Mesajınızı buraya yazın..." | |
| } | |
| } | |
| } | |
| { | |
| "common": { | |
| "actions": { | |
| "logout": "Çıkış Yap", | |
| "back": "Geri", | |
| "create": "Email Oluştur", | |
| "saveChanges": "Değişiklikleri Kaydet", | |
| "saving": "Kaydediliyor...", | |
| "resetToDefaults": "Varsayılanlara Sıfırla", | |
| "close": "Kapat", | |
| "signingOut": "Çıkış yapılıyor...", | |
| "signedOutSuccess": "Çıkış yapıldı!", | |
| "signOutError": "Çıkış yapılırken hata oluştu" | |
| }, | |
| "themes": { | |
| "dark": "Koyu", | |
| "light": "Açık", | |
| "system": "Sistem" | |
| }, | |
| "commandPalette": { | |
| "title": "Komut Paleti", | |
| "description": "Mail-0 için hızlı gezinme ve işlemler", | |
| "placeholder": "Bir komut yazın veya arayın...", | |
| "noResults": "Sonuç bulunamadı", | |
| "groups": { | |
| "mail": "E-posta", | |
| "settings": "Ayarlar", | |
| "actions": "İşlemler", | |
| "help": "Yardım", | |
| "navigation": "Gezinme" | |
| }, | |
| "commands": { | |
| "goToInbox": "Gelen Kutusuna Git", | |
| "goToDrafts": "Taslaklara Git", | |
| "goToSent": "Gönderilenlere Git", | |
| "goToSpam": "Spam'a Git", | |
| "goToArchive": "Arşive Git", | |
| "goToBin": "Çöp Kutusuna Git", | |
| "goToSettings": "Ayarlara Git", | |
| "newEmail": "Yeni E-posta", | |
| "composeMessage": "Mesaj oluştur", | |
| "searchEmails": "E-postaları Ara", | |
| "toggleTheme": "Temayı Değiştir", | |
| "backToMail": "E-postaya Geri Dön", | |
| "goToDocs": "Dokümanlara Git", | |
| "helpWithShortcuts": "Kısayollar Hakkında Yardım" | |
| } | |
| }, | |
| "searchBar": { | |
| "pickDateRange": "Tarih seçin veya aralık seçin", | |
| "search": "Ara", | |
| "clearSearch": "Aramayı temizle", | |
| "advancedSearch": "Gelişmiş arama", | |
| "quickFilters": "Hızlı filtreler", | |
| "searchIn": "Ara", | |
| "recipient": "Alıcı", | |
| "sender": "Gönderen", | |
| "subject": "Konu", | |
| "dateRange": "Tarih aralığı", | |
| "category": "Kategori", | |
| "folder": "Klasör", | |
| "allMail": "Tüm E-postalar", | |
| "unread": "Okunmamış", | |
| "hasAttachment": "Ekli", | |
| "starred": "Yıldızlı", | |
| "applyFilters": "Filtreleri Uygula", | |
| "reset": "Sıfırla" | |
| }, | |
| "navUser": { | |
| "customerSupport": "Müşteri Desteği", | |
| "documentation": "Dokümantasyon", | |
| "appTheme": "Uygulama Teması", | |
| "accounts": "Hesaplar", | |
| "signIn": "Giriş Yap" | |
| }, | |
| "mailCategories": { | |
| "primary": "Birincil", | |
| "important": "Önemli", | |
| "personal": "Kişisel", | |
| "updates": "Güncellemeler", | |
| "promotions": "Tanıtımlar" | |
| }, | |
| "replyCompose": { | |
| "replyTo": "Yanıtla", | |
| "thisEmail": "bu e-postaya", | |
| "dropFiles": "Eklemek için dosyaları bırakın", | |
| "attachments": "Ekler", | |
| "attachmentCount": "{count, plural, =0 {ekler} one {ek} other {ekler}}", | |
| "fileCount": "{count, plural, =0 {dosyalar} one {dosya} other {dosyalar}}", | |
| "saveDraft": "Taslağı kaydet", | |
| "send": "Gönder" | |
| }, | |
| "mailDisplay": { | |
| "details": "Detaylar", | |
| "from": "Kimden", | |
| "to": "Kime", | |
| "cc": "Cc", | |
| "date": "Tarih", | |
| "mailedBy": "Gönderen", | |
| "signedBy": "İmzalayan", | |
| "security": "Güvenlik", | |
| "standardEncryption": "Standart şifreleme (TLS)", | |
| "loadingMailContent": "E-posta içeriği yükleniyor..." | |
| }, | |
| "threadDisplay": { | |
| "close": "Kapat", | |
| "exitFullscreen": "Tam ekrandan çık", | |
| "enterFullscreen": "Tam ekrana geç", | |
| "archive": "Arşivle", | |
| "reply": "Yanıtla", | |
| "moreOptions": "Daha fazla seçenek", | |
| "moveToSpam": "Spam'a taşı", | |
| "replyAll": "Tümünü yanıtla", | |
| "forward": "İlet", | |
| "markAsUnread": "Okunmamış olarak işaretle", | |
| "addLabel": "Etiket ekle", | |
| "muteThread": "Konuyu sessize al" | |
| } | |
| }, | |
| "navigation": { | |
| "sidebar": { | |
| "inbox": "Posta Kutusu", | |
| "drafts": "Taslaklar", | |
| "sent": "Gönderilenler", | |
| "spam": "Spam", | |
| "archive": "Arşiv", | |
| "bin": "Çöp", | |
| "settings": "Ayarlar" | |
| }, | |
| "settings": { | |
| "general": "Genel", | |
| "connections": "Bağlantılar", | |
| "security": "Güvenlik", | |
| "appearance": "Görünüm", | |
| "shortcuts": "Kısayollar" | |
| } | |
| }, | |
| "pages": { | |
| "error": { | |
| "notFound": { | |
| "title": "Sayfa Bulunamadı", | |
| "description": "Üzgünüz! Aradığınız sayfa mevcut değil veya taşınmış.", | |
| "goBack": "Geri Dön" | |
| }, | |
| "settingsNotFound": "404 - Ayarlar sayfası bulunamadı" | |
| }, | |
| "settings": { | |
| "general": { | |
| "title": "Genel Ayarlar", | |
| "description": "Dil ve email görünümü ayarlarını yönetin.", | |
| "language": "Dil", | |
| "timezone": "Saat Dilimi", | |
| "dynamicContent": "Dinamik İçerik", | |
| "dynamicContentDescription": "E-postaların dinamik içerik göstermesine izin verin.", | |
| "externalImages": "Harici Görselleri Göster", | |
| "externalImagesDescription": "E-postaların harici kaynaklardan görselleri göstermesine izin verin." | |
| }, | |
| "connections": { | |
| "title": "E-posta Bağlantıları", | |
| "description": "E-posta hesaplarınızı Zero'ya bağlayın.", | |
| "disconnectTitle": "E-posta Hesabını Bağlantıyı Kes", | |
| "disconnectDescription": "Bu e-posta bağlantısını kesmek istediğinizden emin misiniz?", | |
| "cancel": "İptal", | |
| "remove": "Kaldır", | |
| "disconnectSuccess": "Hesap bağlantısı başarıyla kesildi", | |
| "disconnectError": "Hesap bağlantısı kesilirken hata oluştu", | |
| "addEmail": "E-posta Bağlantısı Ekle", | |
| "connectEmail": "E-posta Bağlantısı", | |
| "connectEmailDescription": "E-posta sağlayıcısını seçin", | |
| "moreComingSoon": "Yakında daha fazla geliyor" | |
| }, | |
| "security": { | |
| "title": "Güvenlik", | |
| "description": "Güvenlik tercihlerinizi ve hesap korumanızı yönetin.", | |
| "twoFactorAuth": "İki Faktörlü Kimlik Doğrulama", | |
| "twoFactorAuthDescription": "Hesabınıza ekstra bir güvenlik katmanı ekleyin", | |
| "loginNotifications": "Giriş Bildirimleri", | |
| "loginNotificationsDescription": "Yeni giriş denemeleri hakkında bildirim alın", | |
| "deleteAccount": "Hesabı Sil" | |
| }, | |
| "appearance": { | |
| "title": "Görünüm", | |
| "description": "Renkleri, yazı tiplerini ve görünüm seçeneklerini özelleştirin.", | |
| "theme": "Tema", | |
| "inboxType": "Gelen Kutusu Tipi" | |
| }, | |
| "shortcuts": { | |
| "title": "Klavye Kısayolları", | |
| "description": "Hızlı işlemler için klavye kısayollarını görüntüleyin ve özelleştirin.", | |
| "actions": { | |
| "newEmail": "Yeni E-posta", | |
| "sendEmail": "E-posta Gönder", | |
| "reply": "Yanıtla", | |
| "replyAll": "Tümünü Yanıtla", | |
| "forward": "İlet", | |
| "drafts": "Taslaklar", | |
| "inbox": "Gelen Kutusu", | |
| "sentMail": "Gönderilen Postalar", | |
| "delete": "Sil", | |
| "search": "Ara", | |
| "markAsUnread": "Okunmadı Olarak İşaretle", | |
| "muteThread": "Konuyu Sessize Al", | |
| "printEmail": "E-postayı Yazdır", | |
| "archiveEmail": "E-postayı Arşivle", | |
| "markAsSpam": "Spam Olarak İşaretle", | |
| "moveToFolder": "Klasöre Taşı", | |
| "undoLastAction": "Son İşlemi Geri Al", | |
| "viewEmailDetails": "E-posta Detaylarını Görüntüle", | |
| "goToDrafts": "Taslaklara Git", | |
| "expandEmailView": "E-posta Görünümünü Genişlet", | |
| "helpWithShortcuts": "Kısayollar Hakkında Yardım" | |
| } | |
| } | |
| }, | |
| "createEmail": { | |
| "body": "İçerik", | |
| "example": "zero@0.email", | |
| "attachments": "Ekler", | |
| "dropFilesToAttach": "Eklemek için dosyaları bırakın", | |
| "writeYourMessageHere": "Mesajınızı buraya yazın...", | |
| "emailSentSuccessfully": "E-posta başarıyla gönderildi", | |
| "failedToSendEmail": "E-posta gönderilemedi. Lütfen tekrar deneyin." | |
| } | |
| } | |
| } |
| loading: t("common.actions.signingOut"), | ||
| success: () => t("common.actions.signedOutSuccess"), | ||
| error: t("common.actions.signOutError"), |
There was a problem hiding this comment.
Use translation function before defining it.
The translation function t is used in these toast message lines before it's actually defined on line 85. This could potentially cause errors.
Move the translation hook declaration above its first usage:
- toast.promise(
- signOut({
- fetchOptions: {
- onSuccess: () => {
- router.push("/");
- },
- },
- }),
- {
- loading: t("common.actions.signingOut"),
- success: () => t("common.actions.signedOutSuccess"),
- error: t("common.actions.signOutError"),
- },
- );
-};
-
-const t = useTranslations();
+ const t = useTranslations();
+
+ toast.promise(
+ signOut({
+ fetchOptions: {
+ onSuccess: () => {
+ router.push("/");
+ },
+ },
+ }),
+ {
+ loading: t("common.actions.signingOut"),
+ success: () => t("common.actions.signedOutSuccess"),
+ error: t("common.actions.signOutError"),
+ },
+ );
+};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| loading: t("common.actions.signingOut"), | |
| success: () => t("common.actions.signedOutSuccess"), | |
| error: t("common.actions.signOutError"), | |
| const t = useTranslations(); | |
| toast.promise( | |
| signOut({ | |
| fetchOptions: { | |
| onSuccess: () => { | |
| router.push("/"); | |
| }, | |
| }, | |
| }), | |
| { | |
| loading: t("common.actions.signingOut"), | |
| success: () => t("common.actions.signedOutSuccess"), | |
| error: t("common.actions.signOutError"), | |
| }, | |
| ); | |
| }; |
| "personal": "Personal", | ||
| "updates": "Updates", | ||
| "promotions": "Promotions", | ||
| "social": "Social" |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Inconsistency between language files - "social" category present in English but missing in Turkish.
This key exists in the English file but not in the Turkish one. Ensure all translation files have the same structure to avoid potential runtime errors.
Add this key to the Turkish translation file to maintain consistency.
| "emailSentSuccessfully": "Email sent successfully", | ||
| "failedToSendEmail": "Failed to send email. Please try again." |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Additional keys missing in Turkish translation.
These email notification strings exist in the English file but not in the Turkish one. Ensure these are added to maintain consistency.
Add these keys to the Turkish translation file.
Description
this PR introduces internationalization (i18n) support
this pr's initial changes have been heavily inspired from #94
changes:
next-intllanguages added currently:
the translations are currently all AI generated but have been doubled checked by me since they are my native tongues
These changes are only for the mail app.
Type of Change
Areas Affected
Testing Done
Describe the tests you've done:
Checklist
Screenshots/Recordings
https://www.loom.com/share/63bbcd44e0cc4c919891cf9fd0d660b9
final:
https://www.loom.com/share/3ba17f111ad64c8193066c031fc506a8
By submitting this pull request, I confirm that my contribution is made under the terms of the project's license.
Summary by CodeRabbit