跳到主要内容
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
开始使用WebReactSwiftUIJetpack ComposeFlutterReact Native预设你自己的设计系统与 AI 代理一起使用 Tanrei品牌设计令牌设计系统与定制Theme Studio颜色字体排印图标形状层级动效交互状态行为布局无障碍文字颜色令牌字阶令牌形状令牌层级令牌动效令牌状态令牌间距与度量令牌组件按钮主操作按钮分段按钮按钮组带菜单的按钮工具栏复制按钮复选框单选按钮开关纸片(Chip)菜单滑块滑块字段日历DateTimePicker字段组单选组复选框组下拉选择框下拉选择字段菜单栏文件上传文件选择器 进阶放置区文本输入框 进阶文本字段纸片字段字段组合框 进阶组合框字段数字输入框 进阶数字字段颜色输入框 进阶颜色字段一次性验证码输入框 进阶一次性验证码字段嵌入式文本输入框 进阶聊天输入栏卡片分隔线模态层 进阶对话框列表列表行底部面板侧边面板轮播代码块表格弹出框步骤折叠面板描述列表图示应用外壳页头树视图时间线图片预览调整手柄可调整大小的框滚动区域徽标进度指示器计量条数字滚动器加载指示器消息条(Snackbar)工具提示键盘快捷键标签(Tag)行内代码提示块(Callout)头像空状态标签组(Tag group)骨架屏文件项色块跑马灯文本标签页标签页视图应用栏导航栏导航抽屉搜索链接分页上一页/下一页链接步骤条面包屑目录链接卡片FAB 菜单状态Tanrei 是如何构建的参与贡献许可与致谢
首页基础文字

文字

组件没有自己的文字。组件绘制或朗读的每一个标签、名称和消息,都是你的产品的——写在组件上,为整个产品只给一次,或者取自你导入的英语文字包。

content_copy以 Markdown 格式查看

组件没有自己的文字。 没有哪个组件带有任何语言的默认文本:没有“必填”,没有“关闭”,也没有“确定”。组件绘制或朗读的文字,都是你的产品给出的:要么是你自己写的,要么取自你导入的英语文字包。

默认值会掩盖它自己造成的失败。一个日语产品漏写了一个词,自己的文字旁边就会出现英语,而且没有任何东西报告这一点。英语产品同样有自己的说法(“Mandatory”“Done”“Nothing found”)。所以组件没有可以退而使用的文字,缺了哪一个,它会告诉你。英语文字包不是这样的后备:除了你自己的导入,没有任何东西会应用它。

文字从哪里来

组件按下面的顺序查看三个地方,不看别处。

地方 它给出什么 示例
组件自身 只与这一个组件有关的文字,以及这一个组件说法不同的文字 label、error-text;close-label="关闭筛选条件"
你的产品的文字 这个部件无论画在哪里,你的说法都一样的文字,每种语言只给一次,或者取自你导入的英语文字包 requiredText、closeLabel、previousMonthLabel
平台 浏览器已经用读者的语言备好的东西 月份和星期的名称、数字、“上午”、缺少值时浏览器自己的提示

这三处之后就什么都没有了:没有后备语言,也没有“暂时先用英语”。

英语文字包

Tanrei 为你写好了一组产品的文字:每个键都有一个平实、中性的英语文字,例如“Required”“Close”和“Previous month”。导入一次即可:

import '@tanrei/web/words/en';

它给出整个页面的文字,就像调用一次 setWords() 一样。不会有任何东西替你导入它:@tanrei/web 不会,@tanrei/react 不会,组件不会,页面的 lang 也不会。每个键在包中的文字,列在键的表格里。

  • 你自己的文字仍然优先。 在导入之后调用 setWords(),你给出的键会替换包中的文字;写在组件上的文字又优先于这两者。导入在运行时会给出每一个键,所以在它之前给出的文字会被替换。
  • 从它的文字开始。 这个模块也把这些文字作为值导出:import { englishWords } from '@tanrei/web/words/en'。
  • 在其他语言的产品里,你那种语言的文字组里没有的键,会从页面的文字组里读取——导入了这个包,那就是英语。只有在你没写的键用英语也合适的地方,才导入它;不导入的话,缺少的文字会被报告。
  • 它不会掩盖不该掩盖的东西。 导入这个包之后,每个键都有文字,所以 missingWords() 为空。要找出你的产品自己还没写的文字,请在不导入这个包的测试里调用它。

在 React 中,导入也是同样的一行,写在最先渲染的模块里。

把产品的文字只给一次

在 Web 上,调用一次 setWords(),在组件加载之前或之后都可以。已经在页面上的元素会立即绘制这些文字。

import { setWords } from '@tanrei/web';

setWords({
  requiredText: '必填',
  optionalText: '选填',
  closeLabel: '关闭',
  clearLabel: '清除',
  targetLabel: '在新标签页中打开',
});

只需给出你用到的组件的文字。第二次调用会按键逐个加到第一次之上;给出空字符串的键会被去掉。@tanrei/web/words 导出同一个函数,但不定义任何元素;@tanrei/react 也导出它。

组件上的属性总是优先于产品的文字:

写在组件自身上的文字:其中一个字段的说法不同

<tanrei-text-field label="邮箱" required required-text="必填"></tanrei-text-field>
<tanrei-text-field label="公司" required required-text="开具发票需要"></tanrei-text-field>

不止一种语言

每种语言给一组。组件读取离它最近的 lang 属性所对应的那一组,所以日语页面,或者英语页面里的日语部分,读到的都是日语的文字。

setWords({ requiredText: 'Required', closeLabel: 'Close' }, { lang: 'en' });
setWords({ requiredText: '必須', closeLabel: '閉じる' }, { lang: 'ja' });
  • 给 ja 的一组也用于 ja-JP,除非你给了 ja-JP 它自己的一组。
  • 某种语言的那一组里没有的键,从不带 lang 给出的那一组里读取。
  • 这些文字是普通的字符串。如果你的产品有本地化库,就把那里的字符串交给 setWords():它不是翻译系统,也没有复数规则。少数几个文字带有由组件填入的占位标记,除此之外,文字里的任何内容都不会被解读。

带占位标记的文字

有些句子里有一个只有组件才知道的值,而且要到说出这句话的那一刻才知道:一行在移动之后排在第几位,或者一次粘贴生成了多少个条目。整个句子仍然由你的产品来写。它用花括号括起一个名称,标出值该放在哪里,组件再把值填到那里。

setWords({ movedText: '{headline}: {position} of {count}' }, { lang: 'en' });
setWords({ movedText: '{headline}:{count} 件中 {position} 番目' }, { lang: 'ja' });

词序是你的语言的词序。组件只填入占位标记,不对句子做别的任何事。

  • 名称由组件决定。 每个键有它自己的占位标记,列在键的表格里,只有这些会被填入。一个占位标记可以写一次、写好几次,也可以不写。
  • 数字按元素所用的语言书写。 {count} 和 {position} 是整数,用最近的 lang 所对应的数字和分组方式书写。
  • 文本保持它自己的方向。 {headline} 和 {entry} 是组件持有的文本:一行的标题、用户键入的一个条目。它们作为纯文本填入,绝不当作标记(markup)。在绘制或朗读这个句子的地方,这段文本会被隔离开,所以阿拉伯语句子里的拉丁字母名称保持它自己的顺序。
  • 要写花括号本身,就连写两个。 {{ 绘制成 {,}} 绘制成 }。不带占位标记的文字根本不会被解读,所以在那里花括号只写一个。
  • 有一个占位标记是必需的。 entryRemoveLabel 为纸片字段每个条目的移除按钮命名。没有 {entry},每个按钮的名称就都一样。

在开发时,控制台会报告这个键没有的占位标记({entri})、单独一个花括号,以及缺少的 {entry}。句子仍然完全按你写的样子使用。setWords() 在你给出文字时报告它们;写在元素上的文字,则由组件在用到它时报告。

复数

组件不懂语法:没有复数形式,没有序数词,也不会在两个句子之间做选择。处理数字有三种办法,哪一种都不需要 Tanrei 做什么。

  1. 写一个对任何数字都成立的句子:第 {position} 项,共 {count} 项、已添加 {count} 项。
  2. 把文字写在组件上,数量变化时更改它。你的产品知道数量,而组件上的文字总是优先。用哪个句子,由你自己的复数规则来选。
  3. 数字由你的产品自己持有的地方,文字不带占位标记:表格的状态(“第 1–20 件,共 132 件”)是由你写的一整个字符串。

没有文字时

缺少文字是产品的缺陷,就像文本字段没有标签一样。组件让它对用户的影响尽量小,并让你看得见它。

  • 本该绘制的文字不绘制。 有标记能表达同样的意思时,标记就站在文字的位置上:必填小标签用星号,选择器的“取消”和“确定”用关闭和对勾的图标,“次日”用 +1。没有这样的标记时,什么都不绘制,那个位置保持原来的大小。
  • 需要名称的控件仍然会绘制,也仍然可以使用。 日历的导航按钮、文本字段的调整手柄和选择器的操作,没有名称也照常工作。唯一的例外是数字字段的步进按钮:没有名称的步进按钮对辅助技术隐藏,因为它旁边的微调按钮自己就能步进。
  • 它会被报告。 在开发时,控制台会指出是哪个属性,每个文字在每个页面上报告一次:<tanrei-field>: required-text is empty and the product's words have no requiredText。

没有无障碍名称的控件,使用屏幕阅读器的人用不了。报告是为了让你去修正,而不是让你可以置之不理。

缺少文字时让测试失败

missingWords() 返回自页面加载以来被需要、却没有人给出的键。在一个把你的产品走一遍的测试的末尾调用它。导入了英语文字包的页面不会缺少任何文字。

import { missingWords } from '@tanrei/web';

test('产品需要的每个文字都已给出', async () => {
  // …渲染产品的各个界面…
  expect(missingWords()).toEqual([]);
});

什么不算文字

字形、星号、短横线、数字,以及由区域设置给出的分隔符,都不是文字,组件仍然会绘制它们。API 里的名称(属性、取值)也不是,控制台告诉开发者的内容也不是,后者是英语。键盘快捷键画出的字符(“Ctrl”“Enter”、⌘)是按键本身带有的;为快捷键朗读的,是你给它的 label。

键

所有的键、读取它们的属性、组件在句子里填入的占位标记,以及它在英语文字包中的文字。大多数键与它的属性同名。有三个不同:复制按钮的 label 和 failedLabel 读取 copyLabel 和 copyFailedLabel,面包屑的 overflowLabel 读取 breadcrumbsOverflowLabel。

键 读取它的属性 填入的位置 英语文字包中的文字
addedLabel code-block.addedLabel Added
attachLabel chat-composer.attachLabel Attach files
avatarGroupOverflowLabel avatar.overflowLabel {count} {count} more
backLabel search.backLabel Back
breadcrumbsOverflowLabel breadcrumbs.overflowLabel Show hidden levels
busyLabel button.busyLabel Loading
cancelLabel date-time-picker.cancelLabel Cancel
cancelledText file-upload.cancelledText, list.cancelledText {label}, {position}, {count} {label} returned to {position} of {count}
characterCountNoLimitText text-input.counterNoLimitText {count} Characters: {count}
characterCountText text-input.counterText {count}, {limit}, {remaining} Characters: {count} of {limit}
clearLabel combobox.clearLabel, navigation-drawer.filterClearLabel, search.clearLabel, tree-view.filterClearLabel Clear
closeLabel bottom-sheet.closeLabel, callout.closeLabel, image-preview.closeLabel, popover.closeLabel, side-sheet.closeLabel, snackbar.closeLabel Close
confirmLabel date-time-picker.confirmLabel OK
copiedLabel code-block.copiedLabel, copy-button.copiedLabel Copied
copyFailedLabel code-block.copyFailedLabel, copy-button.failedLabel Could not copy
copyLabel code-block.copyLabel, copy-button.label Copy
dateLabel date-time-picker.dateLabel Date
decrementLabel number-input.decrementLabel Decrease
dialLabel date-time-picker.dialLabel Dial
dragHandleHint file-upload.dragHandleHint, list.dragHandleHint Press Space to lift, the arrow keys to move, Space to drop, Escape to cancel; Enter opens the moves
dragHandleLabel file-upload.dragHandleLabel, list.dragHandleLabel {label} Reorder {label}
dropZoneRefusingText drop-zone.refusingText These files cannot be added here
dropZoneText drop-zone.dropText Drop files here
duplicateEntryText chip-field.duplicateText This is already in the list.
editLabel tree-view.editLabel Name
emptyText combobox.emptyText No matches
endLabel calendar.endLabel, date-time-picker.endLabel End
entriesAddedText chip-field.addedCountText {count} Added: {count}
entryAddedText chip-field.addedText {entry} {entry} added
entryLimitText chip-field.limitText No more can be added.
entryModeLabel date-time-picker.entryModeLabel Input mode
entryRemoveLabel chip-field.removeLabel {entry} Remove {entry}
entryRemovedText chip-field.removedText {entry} {entry} removed
expandLabel snackbar.expandLabel Show full message
externalLabel link.externalLabel, link-card.externalLabel external link
fileCountErrorText chat-composer.countErrorText, file-upload.countErrorText No more files can be added.
fileSizeErrorText chat-composer.sizeErrorText, file-upload.sizeErrorText This file is too large.
fileTypeErrorText chat-composer.typeErrorText, file-upload.typeErrorText This kind of file cannot be added.
filesDroppedText drop-zone.droppedText {count} Files added: {count}
filesRefusedText drop-zone.refusedText {count} Files not added: {count}
filterCountText navigation-drawer.filterCountText, tree-view.filterCountText {count} Matches: {count}
filterEmptyText navigation-drawer.filterEmptyText, tree-view.filterEmptyText No matches
filterPlaceholder navigation-drawer.filterPlaceholder, tree-view.filterPlaceholder Filter
firstPageLabel pagination.firstLabel First page
handleLabel bottom-sheet.handleLabel Resize sheet
highlightedLabel code-block.highlightedLabel Highlighted
hourLabel date-time-picker.hourLabel Hour
imageCountText image-preview.countText {position}, {count} {position} of {count}
imageErrorText image-preview.errorText This image could not be loaded
incrementLabel number-input.incrementLabel Increase
lastPageLabel pagination.lastLabel Last page
liftedText file-upload.liftedText, list.liftedText {label}, {position}, {count} {label} lifted: {position} of {count}
loadingLabel image-preview.loadingLabel, navigation-drawer.loadingLabel, skeleton.label, tree-view.loadingLabel Loading
marqueePauseLabel marquee-text.pauseLabel Pause scrolling text
marqueePlayLabel marquee-text.playLabel Play scrolling text
menubarOverflowLabel menubar.overflowLabel More
minuteLabel date-time-picker.minuteLabel Minute
moveAfterLabel file-upload.moveAfterLabel, list.moveAfterLabel Move down
moveBeforeLabel file-upload.moveBeforeLabel, list.moveBeforeLabel Move up
moveToEndLabel file-upload.moveToEndLabel, list.moveToEndLabel Move to bottom
moveToStartLabel file-upload.moveToStartLabel, list.moveToStartLabel Move to top
movedText file-upload.movedText, list.movedText {label}, {position}, {count} {label}: {position} of {count}
nextDayLabel date-time-picker.nextDayLabel next day
nextImageLabel image-preview.nextLabel Next image
nextItemLabel carousel.nextLabel Next slide
nextMonthLabel calendar.nextMonthLabel, date-time-picker.nextMonthLabel Next month
nextPageLabel pagination.nextLabel Next page
noFileText file-upload.emptyText No file chosen
noOptionsText select.emptyText No options
optionalText field.optionalText, field-group.optionalText Optional
pageCountText pagination.countText {page}, {count} Page {page} of {count}
pageLabel pagination.pageLabel {page} Page {page}
pauseLabel carousel.pauseLabel Pause slideshow
periodLabel date-time-picker.periodLabel AM/PM
playLabel carousel.playLabel Play slideshow
previousImageLabel image-preview.previousLabel Previous image
previousItemLabel carousel.previousLabel Previous slide
previousMonthLabel calendar.previousMonthLabel, date-time-picker.previousMonthLabel Previous month
previousPageLabel pagination.previousLabel Previous page
rangeErrorText date-time-picker.rangeErrorText End must be after start
rangeLabel date-time-picker.rangeLabel Range
removeLabel file-item.removeLabel {name} Remove {name}
removedLabel code-block.removedLabel Removed
requiredText field.requiredText, field-group.requiredText Required
resizeLabel text-input.resizeLabel Resize
retryLabel file-item.retryLabel {name} Retry {name}
segmentEmptyText text-input.segmentEmptyText Empty
selectAllLabel table.selectAllLabel Select all rows
sendLabel chat-composer.sendLabel Send
sortedAscendingLabel table.sortedAscendingLabel sorted ascending
sortedDescendingLabel table.sortedDescendingLabel sorted descending
startLabel calendar.startLabel, date-time-picker.startLabel Start
stepCompletedText stepper.completedText Completed
stepErrorText stepper.errorText Has an error
stepPositionText stepper.positionText {position}, {count} Step {position} of {count}
stopLabel chat-composer.stopLabel Stop
targetLabel button.targetLabel, card.targetLabel, chip.targetLabel, link.targetLabel, link-card.targetLabel, list/list-item.targetLabel, menu/item.targetLabel, menubar/item.targetLabel, navigation-bar/destination.targetLabel, navigation-drawer/destination.targetLabel, search/result.targetLabel, search/result-group.targetLabel opens in a new tab
todayLabel calendar.todayLabel, date-time-picker.todayLabel Today
toolbarOverflowLabel toolbar.overflowLabel More actions
wheelsLabel date-time-picker.wheelsLabel Wheels
yearViewLabel calendar.yearViewLabel, date-time-picker.yearViewLabel Choose a year
zoomInLabel image-preview.zoomInLabel Zoom in
zoomOutLabel image-preview.zoomOutLabel Zoom out

只与某一样东西有关的文字——字段的 label、日期时间选择器的 label、移除按钮的“移除李华”、带有数量的状态文本——没有键:请在组件上给出它们。

其他平台

这条规则在每个平台上都相同。目前只有 Web 已经实现;SwiftUI、Jetpack Compose、Flutter 和 React Native 将按各自的惯用方式接受产品的文字,以及同一个英语文字包。