Saltar al contenido
menumenu tanreiexpand_morecontrasttranslate
tanreiexpand_more
Primeros pasosWebReactSwiftUIJetpack ComposeFlutterReact NativePreajustesTu propio sistema de diseñoUsar Tanrei con agentes de IAMarcaTokens de diseñoSistemas de diseño y personalizaciónTheme StudioColorTipografíaIconosFormaElevaciónMovimientoEstados de interacciónComportamientoDiseño de páginaAccesibilidadTextosTokens de colorTokens de la escala tipográficaTokens de formaTokens de elevaciónTokens de movimientoTokens de estadoTokens de espacio y de medidaComponentesBotónBotón de acción principalBotón segmentadoGrupo de botonesBotón con menúBarra de herramientasBotón de copiarCasilla de verificaciónBotón de opciónInterruptorChipMenúControl deslizanteCampo de control deslizanteCalendarioDateTimePickerGrupo de camposGrupo de botones de opciónGrupo de casillas de verificaciónSelectCampo de selecciónBarra de menúsSubida de archivosSelector de archivos AvanzadoZona de soltar archivosEntrada de texto AvanzadoCampo de textoCampo de chipsCampoCuadro combinado AvanzadoCampo de cuadro combinadoEntrada numérica AvanzadoCampo numéricoEntrada de color AvanzadoCampo de colorEntrada de código de un solo uso AvanzadoCampo de código de un solo usoEntrada de texto incrustada AvanzadoCuadro de redacción de chatTarjetaDivisorCapa modal AvanzadoDiálogoListaFila de listaHoja inferiorHoja lateralCarruselBloque de códigoTablaPopoverPasosAcordeónLista de descripcionesFiguraEstructura de la aplicaciónCabecera de páginaVista de árbolLínea de tiempoVista previa de imágenesTirador de tamañoMarco redimensionableÁrea de desplazamientoInsigniaIndicador de progresoMedidorContador animadoIndicador de cargaSnackbarTooltipAtajo de tecladoTagCódigo en líneaCalloutAvatarEstado vacíoGrupo de tagsEsqueletoElemento de archivoMuestraTexto en marquesinaPestañasVista de pestañasBarra de la aplicaciónBarra de navegaciónPanel de navegaciónBúsquedaEnlacePaginaciónEnlaces anterior y siguienteIndicador de pasosRuta de navegaciónTabla de contenidoTarjeta de enlaceMenú FABEstadoCómo se construye TanreiCómo contribuirLicencia y atribución
InicioFundamentosTextos

Textos

Un componente no tiene textos propios. Cada etiqueta, cada nombre y cada mensaje que dibuja o dice es de tu producto, dado en el componente, una sola vez para todo el producto o tomado del paquete de textos en inglés que importas.

content_copyVer en Markdown

Un componente no tiene textos propios. Ningún componente tiene un texto predeterminado en ningún idioma: ni «Obligatorio», ni «Cerrar», ni «Aceptar». Un texto que un componente dibuja o dice es uno que dio tu producto: escrito por ti, o tomado del paquete de textos en inglés, que importas tú.

Un valor predeterminado oculta el fallo que provoca. Un producto en japonés que olvida un texto muestra inglés junto a su propio texto, y nada lo notifica. Un producto en inglés también tiene su propia voz («Mandatory», «Done», «Nothing found»). Por eso un componente no tiene ningún texto al que recurrir, y te avisa cuando falta uno. El paquete de textos en inglés no es un recurso así: nada lo aplica salvo tu propia importación.

De dónde sale un texto

Un componente mira en tres lugares, en este orden, y en ningún otro.

Lugar Qué da Ejemplo
El propio componente Los textos sobre esta única cosa, y cualquier texto que esta diga de otra manera label, error-text; close-label="Cerrar filtros"
Los textos de tu producto Los textos que dices igual dondequiera que se dibuje la parte, dados una sola vez por idioma, o el paquete de textos en inglés que importas requiredText, closeLabel, previousMonthLabel
La plataforma Lo que el navegador ya tiene en el idioma del lector Los nombres de los meses y de los días de la semana, las cifras, «a. m.», el mensaje propio del navegador cuando falta un valor

Después de estos no hay nada: ningún idioma de reserva, y ningún inglés «por ahora».

El paquete de textos en inglés

Tanrei escribe para ti un conjunto de los textos de tu producto: un texto en inglés llano y neutro para cada clave, como «Required», «Close» y «Previous month». Impórtalo una vez:

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

Da los textos de toda la página, como lo haría una llamada a setWords(). Nada lo importa por ti: ni @tanrei/web, ni @tanrei/react, ni un componente, ni el lang de la página. El texto del paquete para cada clave está en la tabla de claves.

  • Tus propios textos siguen ganando. Llama a setWords() después de la importación, y las claves que des sustituyen a las del paquete; un texto en un componente gana sobre ambos. La importación da todas las claves cuando se ejecuta, así que un texto que diste antes queda sustituido.
  • Parte de sus textos. El módulo también los exporta: import { englishWords } from '@tanrei/web/words/en'.
  • En un producto en otro idioma, una clave que el conjunto de tu idioma no tiene se lee del conjunto de la página, y con el paquete importado eso es inglés. Impórtalo ahí solo donde el inglés sea el texto que quieres para una clave que no escribiste; sin él, el texto que falta se notifica.
  • No oculta lo que no debe. Con el paquete importado, todas las claves tienen texto, así que missingWords() está vacío. Para encontrar los textos que tu producto aún no ha escrito, pregúntalo en una prueba que no importe el paquete.

En React, la importación es la misma línea, en el módulo que se renderiza primero.

Da los textos de tu producto una sola vez

En la web, llama a setWords() una sola vez, antes o después de que se carguen los componentes. Los elementos que ya están en la página dibujan los textos de inmediato.

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

setWords({
  requiredText: 'Obligatorio',
  optionalText: 'Opcional',
  closeLabel: 'Cerrar',
  clearLabel: 'Borrar',
  targetLabel: 'se abre en una pestaña nueva',
});

Da solo los textos de los componentes que usas. Una segunda llamada se suma a la primera, clave por clave; una clave dada como cadena vacía se retira. @tanrei/web/words exporta la misma función sin definir ningún elemento, y @tanrei/react también la exporta.

Un atributo en un componente siempre tiene prioridad sobre los textos de tu producto:

El texto en el propio componente: un campo lo dice de otra manera

<tanrei-text-field label="Correo electrónico" required required-text="Obligatorio"></tanrei-text-field>
<tanrei-text-field label="Empresa" required required-text="Necesario para las facturas"></tanrei-text-field>

Más de un idioma

Da un conjunto por idioma. Un componente lee el conjunto del atributo lang más cercano que lo rodea, de modo que una página en japonés, o una parte en japonés de una página en inglés, lee los textos en japonés.

setWords({ requiredText: 'Required', closeLabel: 'Close' }, { lang: 'en' });
setWords({ requiredText: '必須', closeLabel: '閉じる' }, { lang: 'ja' });
  • Un conjunto para ja sirve también para ja-JP, salvo que des a ja-JP un conjunto propio.
  • Una clave que el conjunto del idioma no tiene se lee del conjunto dado sin lang.
  • Los textos son cadenas simples. Si tu producto tiene una biblioteca de localización, toma de ella las cadenas que das: setWords() no es un sistema de traducción, y no tiene reglas de plural. Unos pocos textos tienen huecos que rellena un componente, y en un texto no se lee nada más.

Un texto con huecos

Algunas frases contienen un valor que solo el componente conoce, en el momento en que las dice: dónde queda una fila después de moverla, o cuántos elementos creó un pegado. Tu producto sigue escribiendo la frase entera. Marca dónde va el valor con un nombre entre llaves, y el componente pone ahí el valor.

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

El orden de las palabras es el de tu idioma. El componente rellena los huecos y no le hace nada más a la frase.

  • Los nombres los decide el componente. Cada clave tiene sus propios huecos, que figuran en la tabla de claves, y solo esos se rellenan. Puedes escribir un hueco una vez, varias o ninguna.
  • Un número se escribe en el idioma del elemento. {count} y {position} son números enteros, escritos con las cifras y la agrupación del lang más cercano.
  • Un texto conserva su propia dirección. {headline} y {entry} son textos que tiene el componente: el titular de una fila, un elemento que escribió una persona. Se insertan como texto sin formato, nunca como marcado. Donde la frase se dibuja o se anuncia, el texto queda aislado, de modo que un nombre en letras latinas dentro de una frase en árabe conserva su propio orden.
  • Para escribir una llave, escríbela dos veces. {{ se dibuja como { y }} como }. Un texto sin huecos no se lee en absoluto, así que ahí una llave se escribe una sola vez.
  • Un hueco es necesario. entryRemoveLabel da nombre al botón de quitar de cada elemento de un campo de chips. Sin {entry}, todos los botones tienen el mismo nombre.

En desarrollo, la consola notifica un hueco que la clave no tiene ({entri}), una llave suelta y la falta de {entry}. Aun así, la frase se usa exactamente como la escribiste. setWords() los notifica cuando das el texto, y el componente los notifica cuando usa un texto dado en el elemento.

Plurales

El componente no tiene gramática: ni formas de plural, ni ordinales, ni elección entre dos frases. Tienes tres maneras de tratar un número, y ninguna necesita nada de Tanrei.

  1. Escribe una frase que sea correcta para cualquier número: {position} de {count}, Añadidos: {count}.
  2. Da el texto en el componente y cámbialo cuando cambie el recuento. Tu producto conoce el recuento, y un texto dado en el componente siempre tiene prioridad. Tus propias reglas de plural eligen la frase.
  3. Donde es tu producto el que tiene los números, el texto no tiene huecos: el estado de una tabla («1–20 de 132») es una cadena entera que escribes tú.

Sin un texto

Un texto que falta es un defecto del producto, igual que un campo de texto sin etiqueta. El componente hace que sea pequeño para la persona y visible para ti.

  • Los textos que se dibujarían no se dibujan. Donde una marca dice lo mismo, la marca ocupa su lugar: un asterisco en lugar del tag de obligatoriedad de un campo obligatorio, los iconos de cerrar y de marca de verificación en lugar de «Cancelar» y «Aceptar» en un selector, +1 en lugar de «día siguiente». Donde ninguna marca lo dice, no se dibuja nada y el lugar conserva su tamaño.
  • Un control que necesita un nombre se dibuja igualmente y sigue funcionando. Los botones de navegación de un calendario, el tirador de cambio de tamaño de un campo de texto y las acciones de un selector siguen funcionando sin nombre. La única excepción son los botones de paso de un campo numérico: uno sin nombre queda oculto para la tecnología de apoyo, porque el botón de número que tiene al lado da los pasos por sí mismo.
  • Se notifica. En desarrollo, la consola nombra el atributo, una vez por texto y por página: <tanrei-field>: required-text is empty and the product's words have no requiredText.

Un control sin nombre accesible falla a las personas que usan un lector de pantalla. La notificación está ahí para que lo corrijas, no para que puedas dejarlo así.

Haz fallar una prueba cuando falte un texto

missingWords() devuelve las claves que hicieron falta desde que se cargó la página y que nadie dio. Pregúntale al final de una prueba que recorra tu producto. A una página que importa el paquete de textos en inglés no le falta ninguno.

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

test('se dan todos los textos que necesita el producto', async () => {
  // …renderiza las pantallas del producto…
  expect(missingWords()).toEqual([]);
});

Qué no es un texto

Un glifo, un asterisco, una raya, una cifra y un separador que da la configuración regional no son textos, y un componente los sigue dibujando. Tampoco lo son los nombres de la API (una propiedad, un valor), ni lo que la consola le dice a quien desarrolla, que está en inglés. Las leyendas que dibuja un atajo de teclado («Ctrl», «Enter», ⌘) son lo que llevan las propias teclas; lo que se lee en voz alta para un atajo es el label que le das.

Las claves

Todas las claves, las propiedades que las leen, los huecos que el componente rellena en su frase y su texto en el paquete en inglés. La mayoría de las claves tienen el nombre de su propiedad. Tres son distintas: label y failedLabel de un botón de copiar leen copyLabel y copyFailedLabel, y overflowLabel de la ruta de navegación lee breadcrumbsOverflowLabel.

Clave Propiedad que la lee Lugares que rellena En el paquete de textos en inglés
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

Los textos que tratan de una sola cosa (el label de un campo, el label de un selector de fecha y hora, el «Quitar a Ada Lovelace» de un botón de quitar, un texto de estado con un recuento) no tienen clave: dalos en el componente.

Otras plataformas

La regla es la misma en todas las plataformas. Hasta ahora solo está construida la web; SwiftUI, Jetpack Compose, Flutter y React Native recibirán los textos del producto, y el mismo paquete de textos en inglés, con su estilo propio.