Docs
Translating Ballmac UI (i18n)
Every built-in label, placeholder and screen reader text is a message with an English default. One provider translates all 467 of them and sets the locale for dates and numbers.
Quick start
npx shadcn@latest add @ballmac/i18nimport { I18nProvider } from "@/lib/ballmac/i18n"
import ar from "@/messages/ar.json"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ar" dir="rtl">
<body>
<I18nProvider locale="ar" messages={ar}>
{children}
</I18nProvider>
</body>
</html>
)
}messages is a flat object from key to text. Anything you leave out stays in English, so you can translate in stages. Download the complete list of keys with their English text from /i18n/en.json, copy it to ar.json, and translate the values.
Placeholders and plurals
{
"pagination.nextPage": "الصفحة التالية",
"data-table.pageOf": "الصفحة {page} من {total}",
"workspace-card.members_one": "عضو واحد",
"workspace-card.members_two": "عضوان",
"workspace-card.members_few": "{count} أعضاء",
"workspace-card.members_many": "{count} عضوًا",
"workspace-card.members_other": "{count} عضو"
}{name} is replaced with a value. Where a message depends on a number, add one key per plural category your language has (_zero, _one, _two, _few, _many, _other, chosen with Intl.PluralRules); English needs only _one and _other, which are built in.
Locale: dates, numbers and money
The provider's locale is used by every component that formats a number, a date, a currency or a duration. Without a provider, <html lang> is used, then en-US. A locale prop on a single component always wins. To get Western digits in an Arabic UI, use ar-u-nu-latn.
Your own components
"use client"
import { useLocale, useMessages } from "@/lib/ballmac/i18n"
export function Greeting({ name }: { name: string }) {
const msg = useMessages()
return <p>{msg("app.greeting", "Welcome back, {name}", { name })}</p>
}msg.rich fills placeholders with React nodes (for example a <kbd>). If you already use next-intl, i18next or Lingui, keep them for your app and pass their output to I18nProvider as messages.
What is translated, and what is not
- All components: accessible names, placeholders, hidden text, default button and empty-state text, status words and templated sentences.
- Props still win.
closeLabel,emptyMessage,labeland similar props override the dictionary. - Blocks and templates are copy-in starting points. Their headlines and sample copy are props or live in the file, so you edit them as content. The right-to-left layout is covered in RTL.
- Sample values (such as the
.envplaceholder) and brand names are not messages.
All 467 messages
467 of 467 messages
| Key | English |
|---|---|
| activity-feed.emptyMessage | No activity yet |
| agent-plan.progress | {title} progress |
| agent-plan.retry | Retry |
| agent-plan.title | Plan |
| ai-chat.label | Conversation |
| ai-chat.scrollButtonLabel | Scroll to latest message |
| ai-chat.suggestions | Suggestions |
| ai-message.label | Copy message |
| ai-message.messageActions | Message actions |
| api-endpoint.defaultValue | Default: {value} |
| api-endpoint.parameters | {group} parameters |
| api-endpoint.requestAndResponses | Request and responses |
| api-key-field.copied | Copied |
| api-key-field.copyKey | Copy key |
| api-key-field.hideKey | Hide key |
| api-key-field.label | API key |
| api-key-field.regenerateKey | Regenerate key |
| api-key-field.showKey | Show key |
| app-shell.label | Sidebar |
| app-shell.label2 | Details |
| app-shell.navigation | Navigation |
| app-shell.openNavigation | Open navigation |
| approval-card.alwaysAllowLabel | Always allow |
| approval-card.approveLabel | Approve |
| approval-card.deniesItselfIn | Denies itself in {seconds}s |
| approval-card.denyLabel | Deny |
| approval-card.previewLabel | Exactly what will run |
| artifact-panel.close | Close |
| artifact-panel.closeArtifact | Close artifact |
| artifact-panel.copied | Copied |
| artifact-panel.copyCode | Copy code |
| artifact-panel.download | Download |
| artifact-panel.downloadFile | Download {name} |
| artifact-panel.file | file |
| artifact-panel.nextVersion | Next version |
| artifact-panel.previousVersion | Previous version |
| artifact-panel.versionOf | of {total} |
| artifact-panel.versions | Versions |
| artifact-panel.view | View |
| audio-player.audioPlayer | Audio player: {title} |
| audio-player.backSeconds | Back {back} seconds |
| audio-player.chapter | Chapter: |
| audio-player.forwardSeconds | Forward {forward} seconds |
| audio-player.pause | Pause |
| audio-player.play | Play |
| audio-player.playbackSpeedChangeSpeed | Playback speed {rate}×. Change speed |
| audio-player.seek | Seek |
| avatar-circles.andMore | and {extra} more |
| avatar.more | {count} more |
| back-to-top.label | Back to top |
| banner.dismiss | Dismiss {title} |
| billing-card.cancelSubscription | Cancel subscription |
| billing-card.changePlan | Change plan |
| billing-card.currentPlan | Current plan |
| billing-card.downloadInvoice | Download invoice {id} |
| billing-card.exp | Exp {date} |
| billing-card.paymentMethod | Payment method |
| billing-card.recentInvoices | Recent invoices |
| billing-card.seats | Seats |
| billing-card.seatsOf | {used} of {total} used |
| billing-card.seatsUsed | Seats used |
| billing-card.update | Update |
| breadcrumb.label | Breadcrumb |
| breadcrumb.morePages | More pages |
| browser-frame.secureConnection | Secure connection |
| calendar-agenda.agenda | Agenda |
| calendar-agenda.chooseDate | Choose date |
| calendar-agenda.emptyMessage | No events scheduled |
| calendar-agenda.nextDay | Next day |
| calendar-agenda.previousDay | Previous day |
| carousel.chooseSlide | Choose slide |
| carousel.goToSlide | Go to slide {n} |
| carousel.label | Carousel |
| carousel.nextSlide | Next slide |
| carousel.pauseAutoplay | Pause autoplay |
| carousel.previousSlide | Previous slide |
| carousel.startAutoplay | Start autoplay |
| changelog-feed.all | All |
| changelog-feed.filterChanges | Filter changes |
| changelog-feed.latest | Latest |
| changelog-feed.noReleasesMatchThisFilter | No releases match this filter. |
| chat-attachment.label | Attachments |
| chat-attachment.open | Open {name} |
| chat-attachment.remove | Remove {name} |
| chat-attachment.retry | Retry |
| chat-attachment.retryUploading | Retry uploading {name} |
| chat-attachment.uploading | Uploading {name} |
| chat-attachment.uploadingEllipsis | Uploading… |
| chat-attachment.uploadingPercent | Uploading {percent}% |
| citation.nextSource | Next source |
| citation.openSource | Open source |
| citation.previousSource | Previous source |
| citation.sourceOf | Source {n} of {total} |
| code-block.copied | Copied |
| code-block.files | Files |
| code-block.label | Copy code |
| code-block.wrapLines | Wrap lines |
| color-picker.choose | Choose {label} |
| color-picker.hexColor | Hex color |
| color-picker.hexValue | {label} hex value |
| color-picker.label | Color |
| combobox.clearSelection | Clear selection |
| combobox.emptyText | No results found. |
| combobox.placeholder | Select an option |
| combobox.searchPlaceholder | Search |
| command-bar.all | All |
| command-bar.back | Back |
| command-bar.close | Close |
| command-bar.currentPage | Current page |
| command-bar.emptyText | No results found. |
| command-bar.esc | Esc |
| command-bar.navigate | Navigate |
| command-bar.placeholder | Search commands… |
| command-bar.select | Select |
| command-bar.title | Command bar |
| command.description | Type a command or search. Use the arrow keys to move and Enter to run. |
| command.label | Command menu |
| command.title | Command menu |
| comparison-table.available | Available |
| comparison-table.feature | Feature |
| comparison-table.notSpecified | Not specified |
| comparison-table.unavailable | Unavailable |
| contribution-graph.less | Less |
| contribution-graph.more | More |
| contribution-graph.summary | {label}: {total} total across 0 days |
| control-center.label | Control Center |
| control-center.nextTrack | Next track |
| control-center.pause | Pause |
| control-center.play | Play |
| control-center.previousTrack | Previous track |
| cookie-consent.acceptAll | Accept all |
| cookie-consent.alwaysOn | Always on |
| cookie-consent.back | Back |
| cookie-consent.cookiePolicy | Cookie policy |
| cookie-consent.customize | Customize |
| cookie-consent.description | We use cookies to make the site work, understand how it is used and, with your permission, personalize content. |
| cookie-consent.rejectNonEssential | Reject non-essential |
| cookie-consent.savePreferences | Save preferences |
| cookie-consent.title | We value your privacy |
| copy-button.ariaLabel | Copy to clipboard |
| copy-button.copiedLabel | Copied |
| countdown.complete | {label} complete |
| countdown.label | Time remaining |
| data-table.emptyMessage | No results. |
| data-table.nextPage | Next page |
| data-table.pageOf | Page {page} of {total} |
| data-table.previousPage | Previous page |
| data-table.search | Search {label} |
| data-table.searchPlaceholder | Search |
| data-table.selectAllRows | Select all rows on this page |
| data-table.selectRow | Select row {n} |
| data-table.showColumns | Show columns |
| date-picker.clearDate | Clear date |
| date-picker.placeholder | Pick a date |
| date-range-picker.from | From |
| date-range-picker.to | To |
| desktop-icons.label | Desktop |
| dialog.closeLabel | Close |
| diff-viewer.diff | {label} diff |
| diff-viewer.summary | {added} added · {removed} removed |
| dotted-map.label | World map |
| env-editor.addVariable | Add variable |
| env-editor.cancel | Cancel |
| env-editor.hideValueOf | Hide value of {name} |
| env-editor.import | Import |
| env-editor.key | KEY |
| env-editor.nameOfVariable | Name of variable {n} |
| env-editor.noVariablesYet | No variables yet |
| env-editor.pasteAEnvFile | Paste a .env file |
| env-editor.pasteEnv | Paste .env |
| env-editor.remove | Remove {name} |
| env-editor.showValueOf | Show value of {name} |
| env-editor.tipPasteSeveralLinesInto | Tip: paste several lines into any name field to import them. |
| env-editor.title | Environment variables |
| env-editor.toFix | {count} to fix |
| env-editor.value | value |
| env-editor.valueOf | Value of {name} |
| env-editor.variableN | variable {n} |
| feedback-widget.tellUsMore | Tell us more |
| feedback-widget.thankYou | Thank you |
| feedback-widget.topic | Topic |
| feedback-widget.triggerLabel | Feedback |
| feedback-widget.whatWorkedWellOrWhat | What worked well, or what could be better? |
| feedback-widget.yourFeedbackHelpsUsImprove | Your feedback helps us improve. |
| file-browser.clear | Clear |
| file-browser.delete | Delete |
| file-browser.download | Download |
| file-browser.files | Files |
| file-browser.folder | folder |
| file-browser.folderPath | Folder path |
| file-browser.rootLabel | All files |
| file-browser.rowLabel | {name}, {detail} |
| file-browser.searchFiles | Search files |
| file-browser.select | Select {name} |
| file-browser.selectAllFiles | Select all files |
| file-browser.selectedCount | {count} selected |
| file-browser.selectionActions | Selection actions |
| file-browser.view | View |
| file-dropzone.choose | Choose |
| file-dropzone.dropFilesHereOrBrowse | Drop files here or browse |
| file-dropzone.filesToUpload | Files to upload |
| file-dropzone.maxSize | {size} MB each |
| file-dropzone.remove | Remove {name} |
| file-dropzone.sizeKb | {size} KB |
| file-tree.label | Files |
| finder-window.asIcons | as Icons |
| finder-window.asList | as List |
| finder-window.back | Back |
| finder-window.dateModified | Date Modified |
| finder-window.document | document |
| finder-window.forward | Forward |
| finder-window.itemLabel | {name}, {kind} |
| finder-window.name | Name |
| finder-window.path | Path |
| finder-window.search | Search |
| finder-window.searchThisFolder | Search this folder |
| finder-window.size | Size |
| finder-window.view | View |
| floating-nav.label | Primary |
| git-graph.label | Commit history |
| git-graph.merge | Merge |
| globe.label | Rotating globe |
| icon-cloud.label | Items |
| install-tabs.copied | Copied |
| install-tabs.copyCommand | Copy command |
| install-tabs.packageManager | Package manager |
| invite-members.description | They will get an email with a link to join. |
| invite-members.emailAddresses | Email addresses |
| invite-members.nameCompanyCom | name@company.com |
| invite-members.pending | Pending ({count}) |
| invite-members.remove | Remove {chip} |
| invite-members.resendInvitationTo | Resend invitation to {email} |
| invite-members.revokeInvitationFor | Revoke invitation for {email} |
| invite-members.role | Role |
| invite-members.separateAddressesWithAComma | Separate addresses with a comma, space or Enter. You can paste a list. |
| invite-members.title | Invite teammates |
| json-viewer.collapse | Collapse {name} |
| json-viewer.expand | Expand {name} |
| json-viewer.label | JSON data |
| json-viewer.root | root |
| k | Press {key} to open |
| kanban-board.label | Task board |
| kanban-board.moveToNextColumn | Move {title} to next column |
| kanban-board.moveToPreviousColumn | Move {title} to previous column |
| kanban-board.useAltAndArrowKeys | {title}, {column}. Use Alt and arrow keys to move. |
| keyboard-shortcuts.description | Work faster without leaving the keyboard. |
| keyboard-shortcuts.noShortcutMatches | No shortcut matches “{query}”. |
| keyboard-shortcuts.press | Press {key} anywhere to open this list. |
| keyboard-shortcuts.searchPlaceholder | Search shortcuts |
| keyboard-shortcuts.title | Keyboard shortcuts |
| launchpad.label | Launchpad |
| launchpad.noAppsMatch | No apps match “{query}”. |
| launchpad.page | Page {n} |
| launchpad.pageOf | Page {n} of {pages} |
| launchpad.pages | Pages |
| launchpad.search | Search |
| launchpad.searchApps | Search apps |
| lens.label | Zoomable image |
| lens.moveThePointerOverIt | {label}. Move the pointer over it, or use the arrow keys, to magnify. Escape hides the lens. |
| loading-dots.label | Loading |
| lock-screen.enterPassword | Enter Password |
| lock-screen.hint | Touch ID or enter password |
| lock-screen.lockScreen | Lock screen |
| lock-screen.passwordFor | Password for {name} |
| lock-screen.unlock | Unlock |
| log-stream.clear | Clear |
| log-stream.emptyText | Waiting for output… |
| log-stream.filter | Filter |
| log-stream.filterLogLines | Filter log lines |
| log-stream.lines | {title}, {length} lines |
| log-stream.pauseStream | Pause stream |
| log-stream.resumeStream | Resume stream |
| log-stream.showLevels | Show levels |
| log-stream.title | Logs |
| log-stream.wrapLongLines | Wrap long lines |
| mac-window.close | Close window |
| mac-window.minimize | Minimize window |
| mac-window.zoom | Zoom window |
| mega-menu.label | Main |
| mega-menu.learnMore | Learn more |
| model-picker.availableWith | Available with {plan} |
| model-picker.contextTokens | {size} tokens |
| model-picker.costHigh | High |
| model-picker.costLevel | {level} cost |
| model-picker.costLow | Low |
| model-picker.costMedium | Medium |
| model-picker.higherPlan | a higher plan |
| model-picker.locked | Locked |
| model-picker.modelDetails | Model details |
| model-picker.new | New |
| model-picker.placeholder | Select a model |
| model-picker.searchPlaceholder | Search models… |
| multi-select.label | Select options |
| multi-select.noMatchingOptions | No matching options |
| multi-select.placeholder | Choose options |
| multi-select.searchOptions | Search options |
| multi-select.searchOptions2 | Search options… |
| multi-select.selected | {count} selected |
| multi-select.selectedOf | {count} selected of {max} |
| navbar.label | Main |
| navbar.label2 | Menu |
| navbar.siteNavigation | Site navigation |
| notification-center.allNotifications | All notifications |
| notification-center.dismiss | Dismiss "{title}" |
| notification-center.emptyText | You're all caught up. |
| notification-center.filterNotifications | Filter notifications |
| notification-center.label | Notifications |
| notification-center.markAllAsRead | Mark all as read |
| notification-center.markAsRead | Mark "{title}" as read |
| notification-center.unread | Unread |
| notification-center.unreadNotifications | Unread notifications |
| notification-stack.clearAllNotifications | Clear all notifications |
| notification-stack.dismissLabel | Dismiss |
| notification-stack.label | Notifications |
| notification-stack.showAllNotifications | Show all {count} notifications |
| notification-stack.showLess | Show less |
| number-input.decrease | Decrease {label} |
| number-input.increase | Increase {label} |
| number-input.label | Number |
| onboarding-checklist.completeMessage | You're all set. Nice work. |
| onboarding-checklist.description | Finish these steps to set up your workspace. |
| onboarding-checklist.dismissChecklist | Dismiss checklist |
| onboarding-checklist.markDone | {title}: mark as done |
| onboarding-checklist.markNotDone | {title}: mark as not done |
| onboarding-checklist.reviewTheStepsAgain | Review the steps again |
| onboarding-checklist.title | Get started |
| package-badge.perWeek | {count}/wk |
| package-badge.typesIncluded | Types included |
| pagination.label | Pagination |
| pagination.morePages | More pages |
| pagination.nextPage | Next page |
| pagination.previousPage | Previous page |
| password-input.fair | Fair |
| password-input.good | Good |
| password-input.hidePassword | Hide password |
| password-input.label | Password |
| password-input.needsWork | Needs work |
| password-input.showPassword | Show password |
| password-input.strengthHint | Strength: {strength}. Use 8+ characters, mixed case, a number, and a symbol. |
| password-input.strong | Strong |
| password-input.tooShort | Too short |
| phone-input.countryDialingCode | Country dialing code |
| phone-input.label | Phone number |
| plan-selector.billingPeriod | Billing period |
| plan-selector.perMonth | / month |
| plan-selector.save | Save |
| popover.closeLabel | Close popover |
| progress-steps.label | Progress |
| progress-steps.returnTo | Return to {label} |
| prompt-input.attachments | Attachments |
| prompt-input.label | Attach files |
| prompt-input.message | Message |
| prompt-input.placeholder | Send a message… |
| prompt-input.remove | Remove {name} |
| prompt-input.stopLabel | Stop generating |
| prompt-input.submitLabel | Send message |
| rating.clear | Clear |
| rating.label | Rating |
| rating.ofStars | {number} of {count} stars |
| resizable.label | Resize panels |
| scroll-velocity.label | Scrolling content |
| search-field.clearSearch | Clear search |
| search-field.label | Search |
| section-tabs.label | Sections |
| settings-panel.dirtyMessage | You have unsaved changes |
| settings-panel.discard | Discard |
| settings-panel.label | Settings |
| settings-panel.saveLabel | Save changes |
| sheet.closeLabel | Close |
| shortcut-hint.label | {label}: {keys} |
| shortcut-hint.plus | plus |
| sidebar.label | Sidebar |
| sidebar.navigation | Navigation |
| sidebar.toggleSidebar | Toggle sidebar |
| signature-pad.clearSignature | Clear signature |
| signature-pad.draw | Draw |
| signature-pad.drawingArea | {label} drawing area |
| signature-pad.drawOrType | Draw or type |
| signature-pad.signatureMethod | Signature method |
| signature-pad.type | Type |
| signature-pad.typeLabel | Type {label} |
| signature-pad.typeYourFullName | Type your full name |
| signature-pad.undoLastStroke | Undo last stroke |
| slider-range.maximum | {label} maximum |
| slider-range.minimum | {label} minimum |
| snippet-tabs.code | {label} code |
| snippet-tabs.language | Language |
| sources-list.showAll | Show all |
| sources-list.title | Sources |
| sparkline.noData | {label}: no data |
| sparkline.range | {label}: from {from} to {to} |
| spinner.label | Loading |
| split-view.label | List |
| split-view.label2 | Detail |
| split-view.resizeList | Resize list |
| spotlight-search.placeholder | Spotlight Search |
| spotlight-search.title | Spotlight Search |
| spring-drawer.closeLabel | Close |
| spring-drawer.handleLabel | Resize drawer |
| status-badge-row.historyDays | {name} history, {length} days |
| status-badge-row.title | System status |
| status-badge-row.today | Today |
| status-badge-row.uptime | {percent}% uptime |
| stepper-form.back | Back |
| stepper-form.completeLabel | Complete |
| stepper-form.progress | Progress |
| stepper-form.ready | Ready |
| stepper-form.stepCompleted | {title}, step {n} of {total}, completed |
| stepper-form.stepLabel | {title}, step {n} of {total} |
| stepper-form.stepOf | Step {n} of {total} |
| suggestion-chips.label | Suggested prompts |
| suggestion-chips.refreshLabel | Show other suggestions |
| table-of-contents.title | On this page |
| tag-input.label | Tags |
| tag-input.placeholder | Add a tag… |
| tag-input.remove | Remove {tag} |
| team-switcher.addLabel | Add team |
| team-switcher.teams | Teams |
| terminal.copied | Copied |
| terminal.copyCommand | Copy command |
| terminal.terminal | Terminal |
| thinking-indicator.label | Thinking |
| thinking-indicator.statusLabel | Assistant is thinking |
| time-picker.label | Time |
| toast-stack.dismiss | Dismiss {title} |
| toast-stack.label | Notifications |
| token-meter.full | Context is full |
| token-meter.left | {left} left |
| token-meter.nearingLimit | Nearing the limit, {left} left |
| token-meter.pill | {title}: {percent}% used. {state}. |
| token-meter.summary | {used} of {limit} tokens used, {percent}%. {state}. |
| token-meter.title | Context window |
| toolbar.ariaLabel | Search |
| toolbar.clearSearch | Clear search |
| toolbar.search | Search |
| user-menu.signOut | Sign out |
| video-dialog.closeVideo | Close video |
| video-dialog.playVideo | Play video: {title} |
| video-dialog.videoPlayerPressEscapeTo | Video player. Press Escape to close. |
| voice-input.discardRecording | Discard recording |
| voice-input.label | Start voice input |
| voice-input.processingLabel | Transcribing… |
| voice-input.recordingTime | Recording time |
| voice-input.stopAndUseRecording | Stop and use recording |
| webhook-card.events | Events |
| webhook-card.hideSigningSecret | Hide signing secret |
| webhook-card.moreEvents | +{count} more |
| webhook-card.noDeliveriesYetEventsAppear | No deliveries yet. Events appear here as soon as they are sent. |
| webhook-card.recentDeliveries | Recent deliveries |
| webhook-card.redeliver | Redeliver |
| webhook-card.requestBody | Request body |
| webhook-card.revealSigningSecret | Reveal signing secret |
| webhook-card.sendEventsToThisEndpoint | Send events to this endpoint |
| webhook-card.sendTestEvent | Send test event |
| webhook-card.signingSecret | Signing secret |
| webhook-card.successRate | {percent}% of {count} deliveries succeeded |
| webhook-card.webhook | Webhook {url} |
| webhook-card.yourResponse | Your response |
| widgets.noEventsToday | No events today |
| window-manager.label | Desktop |
| window-manager.restore | Restore {title} |
| window-manager.trayLabel | Minimized windows |
| window-manager.windowAltAndArrowKeys | {title} window. Alt and arrow keys move it; Alt, Shift and arrow keys resize it. |
| workspace-card.actions | {name} actions |
| workspace-card.current | Current |
| workspace-card.loadingWorkspace | Loading workspace |
| workspace-card.storageUsed | Storage used |