|
| 1 | +--- |
| 2 | +title: i18n Keys |
| 3 | +description: Translation key conventions and the reference catalog of plugin i18n keys |
| 4 | +--- |
| 5 | + |
| 6 | +Plugins render every user-visible string through `useTranslate()` from `@btst/stack/context`: |
| 7 | + |
| 8 | +```tsx |
| 9 | +const t = useTranslate(); |
| 10 | + |
| 11 | +t("blog.forms.titleLabel", "Title"); |
| 12 | +t("blog.list.tagPageTitle", "{{tag}} Posts", { tag: tag.name }); |
| 13 | +``` |
| 14 | + |
| 15 | +Without an `i18n` provider on `StackProvider`, `t()` returns the English default (with `{{param}}` interpolation), so apps with no provider behave exactly as before. With a provider, your `translate(key, defaultValue, params)` implementation decides what to render. |
| 16 | + |
| 17 | +## Key conventions |
| 18 | + |
| 19 | +- Keys are namespaced `<plugin>.<area>.<name>`, e.g. `blog.forms.titleLabel`. Areas group related UI (`common`, `list`, `card`, `post`, `forms`, `search`, ...). |
| 20 | +- The second argument is always a **string literal** English default, so the key catalog can be regenerated mechanically: |
| 21 | + |
| 22 | +```bash |
| 23 | +pnpm exec tsx packages/stack/scripts/extract-i18n-keys.ts |
| 24 | +``` |
| 25 | + |
| 26 | +- Parameters use `{{param}}` interpolation in the default value. |
| 27 | +- Plugins that support a legacy per-plugin `localization` override object resolve it with higher precedence than `t()`: |
| 28 | + |
| 29 | +```tsx |
| 30 | +localization?.BLOG_FORMS_TITLE_LABEL ?? t("blog.forms.titleLabel", "Title"); |
| 31 | +``` |
| 32 | + |
| 33 | +An app-provided `localization` override wins byte-for-byte; otherwise the string goes through the i18n provider. |
| 34 | + |
| 35 | +## Blog |
| 36 | + |
| 37 | +| Key | Default | |
| 38 | +| --- | --- | |
| 39 | +| `blog.card.draftBadge` | Draft | |
| 40 | +| `blog.common.genericErrorMessage` | An unexpected error occurred. | |
| 41 | +| `blog.common.genericErrorTitle` | Something went wrong | |
| 42 | +| `blog.common.pageNotFoundDescription` | The page you are looking for does not exist. | |
| 43 | +| `blog.common.pageNotFoundTitle` | Not Found | |
| 44 | +| `blog.common.tagsShowAll` | Show all tags | |
| 45 | +| `blog.common.tagsShowLess` | Show fewer tags | |
| 46 | +| `blog.forms.cancelButton` | Cancel | |
| 47 | +| `blog.forms.contentLabel` | Content | |
| 48 | +| `blog.forms.deleteButton` | Delete Post | |
| 49 | +| `blog.forms.deleteDialogCancel` | Cancel | |
| 50 | +| `blog.forms.deleteDialogConfirm` | Delete | |
| 51 | +| `blog.forms.deleteDialogDescription` | Are you sure you want to delete this post? This action cannot be undone. | |
| 52 | +| `blog.forms.deleteDialogTitle` | Delete Post | |
| 53 | +| `blog.forms.deletePending` | Deleting... | |
| 54 | +| `blog.forms.editorPlaceholder` | Write something... | |
| 55 | +| `blog.forms.excerptLabel` | Excerpt | |
| 56 | +| `blog.forms.excerptPlaceholder` | Brief summary of your post... | |
| 57 | +| `blog.forms.featuredImageErrorNotImage` | Please select an image file | |
| 58 | +| `blog.forms.featuredImageErrorTooLarge` | Image size must be less than 4MB | |
| 59 | +| `blog.forms.featuredImageInputPlaceholder` | Image URL or upload below... | |
| 60 | +| `blog.forms.featuredImageLabel` | Image | |
| 61 | +| `blog.forms.featuredImagePreviewAlt` | Featured image preview | |
| 62 | +| `blog.forms.featuredImageRequiredAsterisk` | * | |
| 63 | +| `blog.forms.featuredImageToastFailure` | Failed to upload image | |
| 64 | +| `blog.forms.featuredImageToastSuccess` | Image uploaded successfully | |
| 65 | +| `blog.forms.featuredImageUploadButton` | Upload | |
| 66 | +| `blog.forms.featuredImageUploadingButton` | Uploading... | |
| 67 | +| `blog.forms.featuredImageUploadingText` | Uploading image... | |
| 68 | +| `blog.forms.publishedDescription` | Toggle to publish immediately | |
| 69 | +| `blog.forms.publishedLabel` | Published | |
| 70 | +| `blog.forms.requiredAsterisk` | * | |
| 71 | +| `blog.forms.slugLabel` | Slug | |
| 72 | +| `blog.forms.slugPlaceholder` | url-friendly-slug | |
| 73 | +| `blog.forms.submitCreateIdle` | Create Post | |
| 74 | +| `blog.forms.submitCreatePending` | Creating... | |
| 75 | +| `blog.forms.submitUpdateIdle` | Update Post | |
| 76 | +| `blog.forms.submitUpdatePending` | Updating... | |
| 77 | +| `blog.forms.tagsLabel` | Tags | |
| 78 | +| `blog.forms.tagsPlaceholder` | Enter your post tags... | |
| 79 | +| `blog.forms.tagsSearchPlaceholder` | Search or create tags... | |
| 80 | +| `blog.forms.titleLabel` | Title | |
| 81 | +| `blog.forms.titlePlaceholder` | Enter your post title... | |
| 82 | +| `blog.forms.toastCreateSuccess` | Post created successfully | |
| 83 | +| `blog.forms.toastDeleteFailure` | Failed to delete post | |
| 84 | +| `blog.forms.toastDeleteSuccess` | Post deleted successfully | |
| 85 | +| `blog.forms.toastUpdateSuccess` | Post updated successfully | |
| 86 | +| `blog.forms.validation.contentRequired` | Content is required | |
| 87 | +| `blog.forms.validation.excerptRequired` | Excerpt is required | |
| 88 | +| `blog.forms.validation.slugRequired` | Slug is required | |
| 89 | +| `blog.forms.validation.titleRequired` | Title is required | |
| 90 | +| `blog.list.draftsTitle` | Draft Posts | |
| 91 | +| `blog.list.empty` | There are no posts here yet. | |
| 92 | +| `blog.list.loadMore` | Load more posts | |
| 93 | +| `blog.list.loadingMore` | Loading more... | |
| 94 | +| `blog.list.searchButton` | Search Posts | |
| 95 | +| `blog.list.searchEmpty` | No blog posts found. | |
| 96 | +| `blog.list.searchPlaceholder` | Search Blog Posts... | |
| 97 | +| `blog.list.tagNotFound` | Tag not found | |
| 98 | +| `blog.list.tagNotFoundDescription` | The tag you are looking for does not exist. | |
| 99 | +| `blog.list.tagPageDescription` | Browse all posts with this tag | |
| 100 | +| `blog.list.tagPageTitle` | \{\{tag\}\} Posts | |
| 101 | +| `blog.list.title` | Blog Posts | |
| 102 | +| `blog.post.addDescription` | Create a new blog post. | |
| 103 | +| `blog.post.addTitle` | Add New Post | |
| 104 | +| `blog.post.editDescription` | Update your blog post. | |
| 105 | +| `blog.post.editTitle` | Edit Post | |
| 106 | +| `blog.post.keepReading` | Keep Reading | |
| 107 | +| `blog.post.next` | Next | |
| 108 | +| `blog.post.onThisPage` | In This Post | |
| 109 | +| `blog.post.previous` | Previous | |
| 110 | +| `blog.post.viewAll` | View all | |
| 111 | +| `blog.search.button` | Search | |
| 112 | +| `blog.search.empty` | No results found. | |
| 113 | +| `blog.search.placeholder` | Type to search... | |
| 114 | +| `blog.search.searching` | Searching... | |
| 115 | + |
| 116 | +Other plugins adopt the same convention as their phase-2 sweeps land. |
0 commit comments