From c36df5ecc1fae4c630683e5b104b943a47cb8820 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Fri, 11 Aug 2023 07:27:34 -0700 Subject: [PATCH] (#1483) * copy updates * Update docs/pages/components/copy.md Co-authored-by: Thomas Allmer * unwrap and fix case * copy button updates * use bs icon * add parts, hoist, and improve parsing a bit * update docs * remove comment --------- Co-authored-by: Thomas Allmer --- docs/pages/components/copy-button.md | 258 ++++++++++++++++++ docs/pages/components/icon.md | 4 +- docs/pages/resources/changelog.md | 2 + .../copy-button/copy-button.component.ts | 257 +++++++++++++++++ .../copy-button/copy-button.styles.ts | 49 ++++ .../copy-button/copy-button.test.ts | 20 ++ src/components/copy-button/copy-button.ts | 4 + src/components/icon/library.system.ts | 7 +- src/components/tooltip/tooltip.component.ts | 2 - src/components/tooltip/tooltip.styles.ts | 1 + src/events/events.ts | 1 + src/events/sl-copy.ts | 9 + src/shoelace.ts | 1 + src/translations/da.ts | 2 + src/translations/de.ts | 2 + src/translations/en.ts | 2 + src/translations/es.ts | 2 + src/translations/fa.ts | 2 + src/translations/fr.ts | 2 + src/translations/he.ts | 2 + src/translations/hu.ts | 2 + src/translations/ja.ts | 2 + src/translations/nl.ts | 2 + src/translations/pl.ts | 2 + src/translations/pt.ts | 2 + src/translations/ru.ts | 2 + src/translations/sv.ts | 2 + src/translations/tr.ts | 2 + src/translations/zh-tw.ts | 2 + src/utilities/localize.ts | 2 + 30 files changed, 643 insertions(+), 6 deletions(-) create mode 100644 docs/pages/components/copy-button.md create mode 100644 src/components/copy-button/copy-button.component.ts create mode 100644 src/components/copy-button/copy-button.styles.ts create mode 100644 src/components/copy-button/copy-button.test.ts create mode 100644 src/components/copy-button/copy-button.ts create mode 100644 src/events/sl-copy.ts diff --git a/docs/pages/components/copy-button.md b/docs/pages/components/copy-button.md new file mode 100644 index 000000000..682f92b54 --- /dev/null +++ b/docs/pages/components/copy-button.md @@ -0,0 +1,258 @@ +--- +meta: + title: Copy Button + description: Copies data to the clipboard when the user clicks the button. +layout: component +--- + +```html:preview + +``` + +```jsx:react +import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button'; + +const App = () => ( + +); +``` + +## Examples + +### Custom Labels + +Copy Buttons display feedback in a tooltip. You can customize the labels using the `copy-label`, `success-label`, and `error-label` attributes. + +```html:preview + +``` + +```jsx:react +import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button'; + +const App = () => ( + +); +``` + +### Custom Icons + +Use the `copy-icon`, `success-icon`, and `error-icon` slots to customize the icons that get displayed for each state. You can use [``](/components/icon) or your own images. + +```html:preview + + + + + +``` + +```jsx:react +import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button'; +import { SlIcon } from '@shoelace-style/shoelace/dist/react/sl-icon'; + +const App = () => ( + <> + + + + + + +); +``` + +### Copying Values From Other Elements + +Normally, the data that gets copied will come from the component's `value` attribute, but you can copy data from any element within the same document by providing its `id` to the `from` attribute. + +When using the `from` attribute, the element's [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) will be copied by default. Passing an attribute or property modifier will let you copy data from one of the element's attributes or properties instead. + +To copy data from an attribute, use `from="id[attr]"` where `id` is the id of the target element and `attr` is the name of the attribute you'd like to copy. To copy data from a property, use `from="id.prop"` where `id` is the id of the target element and `prop` is the name of the property you'd like to copy. + +```html:preview + ++1 (234) 456-7890 + + +

+ + + + + +

+ + +Shoelace Website + +``` + +```jsx:react +import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button'; +import { SlInput } from '@shoelace-style/shoelace/dist/react/sl-input'; + +const App = () => ( + <> + {/* Copies the span's textContent */} + +1 (234) 456-7890 + + +

+ + {/* Copies the input's "value" property */} + + + +

+ + {/* Copies the link's "href" attribute */} + Shoelace Website + + +); +``` + +### Handling Errors + +A copy error will occur if the value is an empty string, if the `from` attribute points to an id that doesn't exist, or if the browser rejects the operation for any reason. When this happens, the `sl-error` event will be emitted. + +This example demonstrates what happens when a copy error occurs. You can customize the error label and icon using the `error-label` attribute and the `error-icon` slot, respectively. + +```html:preview + +``` + +```jsx:react +import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button'; + +const App = () => ( + +); +``` + +### Disabled + +Copy buttons can be disabled by adding the `disabled` attribute. + +```html:preview + +``` + +```jsx:react +import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button'; + +const App = () => ( + +); +``` + +### Changing Feedback Duration + +A success indicator is briefly shown after copying. You can customize the length of time the indicator is shown using the `feedback-duration` attribute. + +```html:preview + +``` + +```jsx:react +import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button'; + +const App = () => ( + +); +``` + +### Custom Styles + +You can customize the button to your liking with CSS. + +```html:preview + + + + + + + +``` + +```jsx:react +import { SlCopyButton } from '@shoelace-style/shoelace/dist/react/sl-copy-button'; + +const css = ` + .custom-styles { + --success-color: white; + --error-color: white; + color: white; + } + + .custom-styles::part(button) { + background-color: #ff1493; + border: solid 4px #ff7ac1; + border-right-color: #ad005c; + border-bottom-color: #ad005c; + border-radius: 0; + transition: 100ms scale ease-in-out, 100ms translate ease-in-out; + } + + .custom-styles::part(button):hover { + scale: 1.1; + } + + .custom-styles::part(button):active { + translate: 0 2px; + } + + .custom-styles::part(button):focus-visible { + outline: dashed 2px deeppink; + outline-offset: 4px; + } +`; + +const App = () => ( + <> + + + + +); +``` diff --git a/docs/pages/components/icon.md b/docs/pages/components/icon.md index 93d1f27a5..97366c3fa 100644 --- a/docs/pages/components/icon.md +++ b/docs/pages/components/icon.md @@ -645,9 +645,7 @@ When using sprite sheets, the `sl-load` and `sl-error` events will not fire. ::: :::danger -For security reasons, browsers may apply the same-origin policy on `` elements located in the `` shadow dom and -may refuse to load a cross-origin URL. There is currently no defined way to set a cross-origin policy for `` elements. -For this reason, sprite sheets should only be used if you're self-hosting them. +For security reasons, browsers may apply the same-origin policy on `` elements located in the `` shadow DOM and may refuse to load a cross-origin URL. There is currently no defined way to set a cross-origin policy for `` elements. For this reason, sprite sheets should only be used if you're self-hosting them. ::: ```html:preview diff --git a/docs/pages/resources/changelog.md b/docs/pages/resources/changelog.md index b6ee3fe63..d5550563e 100644 --- a/docs/pages/resources/changelog.md +++ b/docs/pages/resources/changelog.md @@ -14,8 +14,10 @@ New versions of Shoelace are released as-needed and generally occur when a criti ## Next +- Added the `` component [#1473] - Fixed a bug in `` where pressing [[Up]] or [[Down]] when focused on the trigger wouldn't focus the first/last menu items [#1472] - Improved the behavior of the clear button in `` to prevent the component's width from shifting when toggled [#1496] +- Improved `` to prevent user selection so the tooltip doesn't get highlighted when dragging selections - Removed `sideEffects` key from `package.json`. Update React docs to use cherry-picking. [#1485] - Updated Bootstrap Icons to 1.10.5 diff --git a/src/components/copy-button/copy-button.component.ts b/src/components/copy-button/copy-button.component.ts new file mode 100644 index 000000000..fbd8f4504 --- /dev/null +++ b/src/components/copy-button/copy-button.component.ts @@ -0,0 +1,257 @@ +import { classMap } from 'lit/directives/class-map.js'; +import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js'; +import { html } from 'lit'; +import { LocalizeController } from '../../utilities/localize.js'; +import { property, query, state } from 'lit/decorators.js'; +import ShoelaceElement from '../../internal/shoelace-element.js'; +import SlIcon from '../icon/icon.component.js'; +import SlTooltip from '../tooltip/tooltip.component.js'; +import styles from './copy-button.styles.js'; +import type { CSSResultGroup } from 'lit'; + +/** + * @summary Copies text data to the clipboard when the user clicks the trigger. + * @documentation https://shoelace.style/components/copy + * @status experimental + * @since 2.7 + * + * @dependency sl-icon + * @dependency sl-tooltip + * + * @event sl-copy - Emitted when the data has been copied. + * @event sl-error - Emitted when the data could not be copied. + * + * @slot copy-icon - The icon to show in the default copy state. Works best with ``. + * @slot success-icon - The icon to show when the content is copied. Works best with ``. + * @slot error-icon - The icon to show when a copy error occurs. Works best with ``. + * + * @csspart button - The internal ` + + `; + } +} + +setDefaultAnimation('copy.in', { + keyframes: [ + { scale: '.25', opacity: '.25' }, + { scale: '1', opacity: '1' } + ], + options: { duration: 100 } +}); + +setDefaultAnimation('copy.out', { + keyframes: [ + { scale: '1', opacity: '1' }, + { scale: '.25', opacity: '0' } + ], + options: { duration: 100 } +}); + +declare global { + interface HTMLElementTagNameMap { + 'sl-copy-button': SlCopyButton; + } +} diff --git a/src/components/copy-button/copy-button.styles.ts b/src/components/copy-button/copy-button.styles.ts new file mode 100644 index 000000000..29cd4cfb6 --- /dev/null +++ b/src/components/copy-button/copy-button.styles.ts @@ -0,0 +1,49 @@ +import { css } from 'lit'; +import componentStyles from '../../styles/component.styles.js'; + +export default css` + ${componentStyles} + + :host { + --error-color: var(--sl-color-danger-600); + --success-color: var(--sl-color-success-600); + + display: inline-block; + } + + .copy-button__button { + flex: 0 0 auto; + display: flex; + align-items: center; + background: none; + border: none; + border-radius: var(--sl-border-radius-medium); + font-size: inherit; + color: inherit; + padding: var(--sl-spacing-x-small); + cursor: pointer; + transition: var(--sl-transition-x-fast) color; + } + + .copy-button--success .copy-button__button { + color: var(--success-color); + } + + .copy-button--error .copy-button__button { + color: var(--error-color); + } + + .copy-button__button:focus-visible { + outline: var(--sl-focus-ring); + outline-offset: var(--sl-focus-ring-offset); + } + + .copy-button__button[disabled] { + opacity: 0.5; + cursor: not-allowed !important; + } + + slot { + display: inline-flex; + } +`; diff --git a/src/components/copy-button/copy-button.test.ts b/src/components/copy-button/copy-button.test.ts new file mode 100644 index 000000000..79eb344c1 --- /dev/null +++ b/src/components/copy-button/copy-button.test.ts @@ -0,0 +1,20 @@ +import '../../../dist/shoelace.js'; +import { expect, fixture, html } from '@open-wc/testing'; +import type SlCopyButton from './copy-button.js'; + +// We use aria-live to announce labels via tooltips +const ignoredRules = ['button-name']; + +describe('', () => { + let el: SlCopyButton; + + describe('when provided no parameters', () => { + before(async () => { + el = await fixture(html` `); + }); + + it('should pass accessibility tests', async () => { + await expect(el).to.be.accessible({ ignoredRules }); + }); + }); +}); diff --git a/src/components/copy-button/copy-button.ts b/src/components/copy-button/copy-button.ts new file mode 100644 index 000000000..0283a1e80 --- /dev/null +++ b/src/components/copy-button/copy-button.ts @@ -0,0 +1,4 @@ +import SlCopyButton from './copy-button.component.js'; +export * from './copy-button.component.js'; +export default SlCopyButton; +SlCopyButton.define('sl-copy-button'); diff --git a/src/components/icon/library.system.ts b/src/components/icon/library.system.ts index a0b6ab3ea..080642e92 100644 --- a/src/components/icon/library.system.ts +++ b/src/components/icon/library.system.ts @@ -16,7 +16,7 @@ const icons = { check: ` - + @@ -40,6 +40,11 @@ const icons = { `, + copy: ` + + + + `, eye: ` diff --git a/src/components/tooltip/tooltip.component.ts b/src/components/tooltip/tooltip.component.ts index cc8fa2ca9..4341f344b 100644 --- a/src/components/tooltip/tooltip.component.ts +++ b/src/components/tooltip/tooltip.component.ts @@ -99,8 +99,6 @@ export default class SlTooltip extends ShoelaceElement { constructor() { super(); - // TODO (justinfagnani): does this need to be done in firstUpdated for some - // reason? If so, document why in a comment. this.addEventListener('blur', this.handleBlur, true); this.addEventListener('focus', this.handleFocus, true); this.addEventListener('click', this.handleClick); diff --git a/src/components/tooltip/tooltip.styles.ts b/src/components/tooltip/tooltip.styles.ts index a88bf1f61..c4d9d6db8 100644 --- a/src/components/tooltip/tooltip.styles.ts +++ b/src/components/tooltip/tooltip.styles.ts @@ -51,5 +51,6 @@ export default css` color: var(--sl-tooltip-color); padding: var(--sl-tooltip-padding); pointer-events: none; + user-select: none; } `; diff --git a/src/events/events.ts b/src/events/events.ts index 913ee9870..739fcd679 100644 --- a/src/events/events.ts +++ b/src/events/events.ts @@ -8,6 +8,7 @@ export type { default as SlChangeEvent } from './sl-change'; export type { default as SlClearEvent } from './sl-clear'; export type { default as SlCloseEvent } from './sl-close'; export type { default as SlCollapseEvent } from './sl-collapse'; +export type { default as SlCopyEvent } from './sl-copy'; export type { default as SlErrorEvent } from './sl-error'; export type { default as SlExpandEvent } from './sl-expand'; export type { default as SlFinishEvent } from './sl-finish'; diff --git a/src/events/sl-copy.ts b/src/events/sl-copy.ts new file mode 100644 index 000000000..65e697b59 --- /dev/null +++ b/src/events/sl-copy.ts @@ -0,0 +1,9 @@ +type SlCopyEvent = CustomEvent<{ value: string }>; + +declare global { + interface GlobalEventHandlersEventMap { + 'sl-copy': SlCopyEvent; + } +} + +export default SlCopyEvent; diff --git a/src/shoelace.ts b/src/shoelace.ts index 1fff365fd..e6660e9d4 100644 --- a/src/shoelace.ts +++ b/src/shoelace.ts @@ -13,6 +13,7 @@ export { default as SlCarousel } from './components/carousel/carousel.js'; export { default as SlCarouselItem } from './components/carousel-item/carousel-item.js'; export { default as SlCheckbox } from './components/checkbox/checkbox.js'; export { default as SlColorPicker } from './components/color-picker/color-picker.js'; +export { default as SlCopyButton } from './components/copy-button/copy-button.js'; export { default as SlDetails } from './components/details/details.js'; export { default as SlDialog } from './components/dialog/dialog.js'; export { default as SlDivider } from './components/divider/divider.js'; diff --git a/src/translations/da.ts b/src/translations/da.ts index e0d459c8c..3925e68d5 100644 --- a/src/translations/da.ts +++ b/src/translations/da.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'Karrusel', clearEntry: 'Ryd indtastning', close: 'Luk', + copied: 'Kopieret', copy: 'Kopier', currentValue: 'Nuværende værdi', + error: 'Fejl', goToSlide: (slide, count) => `Gå til dias ${slide} af ${count}`, hidePassword: 'Skjul adgangskode', loading: 'Indlæser', diff --git a/src/translations/de.ts b/src/translations/de.ts index 4c1df4fba..1894e6006 100644 --- a/src/translations/de.ts +++ b/src/translations/de.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'Karussell', clearEntry: 'Eingabe löschen', close: 'Schließen', + copied: 'Kopiert', copy: 'Kopieren', currentValue: 'Aktueller Wert', + error: 'Fehler', goToSlide: (slide, count) => `Gehen Sie zu Folie ${slide} von ${count}`, hidePassword: 'Passwort verbergen', loading: 'Wird geladen', diff --git a/src/translations/en.ts b/src/translations/en.ts index 86a5e015f..155cfcfd9 100644 --- a/src/translations/en.ts +++ b/src/translations/en.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'Carousel', clearEntry: 'Clear entry', close: 'Close', + copied: 'Copied', copy: 'Copy', currentValue: 'Current value', + error: 'Error', goToSlide: (slide, count) => `Go to slide ${slide} of ${count}`, hidePassword: 'Hide password', loading: 'Loading', diff --git a/src/translations/es.ts b/src/translations/es.ts index d15fdff74..4e9489673 100644 --- a/src/translations/es.ts +++ b/src/translations/es.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'Carrusel', clearEntry: 'Borrar entrada', close: 'Cerrar', + copied: 'Copiado', copy: 'Copiar', currentValue: 'Valor actual', + error: 'Error', goToSlide: (slide, count) => `Ir a la diapositiva ${slide} de ${count}`, hidePassword: 'Ocultar contraseña', loading: 'Cargando', diff --git a/src/translations/fa.ts b/src/translations/fa.ts index 2f39bde1a..6b70bddbb 100644 --- a/src/translations/fa.ts +++ b/src/translations/fa.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'چرخ فلک', clearEntry: 'پاک کردن ورودی', close: 'بستن', + copied: 'کپی شد', copy: 'رونوشت', currentValue: 'مقدار فعلی', + error: 'خطا', goToSlide: (slide, count) => `رفتن به اسلاید ${slide} از ${count}`, hidePassword: 'پنهان کردن رمز', loading: 'بارگذاری', diff --git a/src/translations/fr.ts b/src/translations/fr.ts index a760fe841..e1386c976 100644 --- a/src/translations/fr.ts +++ b/src/translations/fr.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'Carrousel', clearEntry: `Effacer l'entrée`, close: 'Fermer', + copied: 'Copié', copy: 'Copier', currentValue: 'Valeur actuelle', + error: 'Erreur', goToSlide: (slide, count) => `Aller à la diapositive ${slide} de ${count}`, hidePassword: 'Masquer le mot de passe', loading: 'Chargement', diff --git a/src/translations/he.ts b/src/translations/he.ts index 591878045..91d0f2c99 100644 --- a/src/translations/he.ts +++ b/src/translations/he.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'קרוסלה', clearEntry: 'נקה קלט', close: 'סגור', + copied: 'מוּעֲתָק', copy: 'העתק', currentValue: 'ערך נוכחי', + error: 'שְׁגִיאָה', goToSlide: (slide, count) => `עבור לשקופית ${slide} של ${count}`, hidePassword: 'הסתר סיסמא', loading: 'טוען', diff --git a/src/translations/hu.ts b/src/translations/hu.ts index 7ff13be2b..8272af856 100644 --- a/src/translations/hu.ts +++ b/src/translations/hu.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'Körhinta', clearEntry: 'Bejegyzés törlése', close: 'Bezárás', + copied: 'Másolva', copy: 'Másolás', currentValue: 'Aktuális érték', + error: 'Hiba', goToSlide: (slide, count) => `Ugrás a ${count}/${slide}. diára`, hidePassword: 'Jelszó elrejtése', loading: 'Betöltés', diff --git a/src/translations/ja.ts b/src/translations/ja.ts index 47217e808..62ba4ed6c 100644 --- a/src/translations/ja.ts +++ b/src/translations/ja.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'カルーセル', clearEntry: 'クリアエントリ', close: '閉じる', + copied: 'コピーされました', copy: 'コピー', currentValue: '現在の価値', + error: 'エラー', goToSlide: (slide, count) => `${count} 枚中 ${slide} 枚のスライドに移動`, hidePassword: 'パスワードを隠す', loading: '読み込み中', diff --git a/src/translations/nl.ts b/src/translations/nl.ts index a4c859b85..6357fa7b2 100644 --- a/src/translations/nl.ts +++ b/src/translations/nl.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'Carrousel', clearEntry: 'Invoer wissen', close: 'Sluiten', + copied: 'Gekopieerd', copy: 'Kopiëren', currentValue: 'Huidige waarde', + error: 'Fout', goToSlide: (slide, count) => `Ga naar slide ${slide} van ${count}`, hidePassword: 'Verberg wachtwoord', loading: 'Bezig met laden', diff --git a/src/translations/pl.ts b/src/translations/pl.ts index 5630a4e03..b22913c66 100644 --- a/src/translations/pl.ts +++ b/src/translations/pl.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'Karuzela', clearEntry: 'Wyczyść wpis', close: 'Zamknij', + copied: 'Skopiowane', copy: 'Kopiuj', currentValue: 'Aktualna wartość', + error: 'Błąd', goToSlide: (slide, count) => `Przejdź do slajdu ${slide} z ${count}`, hidePassword: 'Ukryj hasło', loading: 'Ładowanie', diff --git a/src/translations/pt.ts b/src/translations/pt.ts index b433c6a3f..c093efac4 100644 --- a/src/translations/pt.ts +++ b/src/translations/pt.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'Carrossel', clearEntry: 'Limpar entrada', close: 'Fechar', + copied: 'Copiado', copy: 'Copiar', currentValue: 'Valor atual', + error: 'Erro', goToSlide: (slide, count) => `Vá para o slide ${slide} de ${count}`, hidePassword: 'Esconder a senha', loading: 'Carregando', diff --git a/src/translations/ru.ts b/src/translations/ru.ts index ee9f14722..87eae99fb 100644 --- a/src/translations/ru.ts +++ b/src/translations/ru.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'Карусель', clearEntry: 'Очистить запись', close: 'Закрыть', + copied: 'Скопировано', copy: 'Скопировать', currentValue: 'Текущее значение', + error: 'Ошибка', goToSlide: (slide, count) => `Перейти к слайду ${slide} из ${count}`, hidePassword: 'Скрыть пароль', loading: 'Загрузка', diff --git a/src/translations/sv.ts b/src/translations/sv.ts index 9c1015c88..e9c9f546f 100644 --- a/src/translations/sv.ts +++ b/src/translations/sv.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'Karusell', clearEntry: 'Återställ val', close: 'Stäng', + copied: 'Kopierade', copy: 'Kopiera', currentValue: 'Nuvarande värde', + error: 'Fel', goToSlide: (slide, count) => `Gå till bild ${slide} av ${count}`, hidePassword: 'Dölj lösenord', loading: 'Läser in', diff --git a/src/translations/tr.ts b/src/translations/tr.ts index 1d983a92b..5635963a7 100644 --- a/src/translations/tr.ts +++ b/src/translations/tr.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: 'Atlıkarınca', clearEntry: 'Girişi sil', close: 'Kapat', + copied: 'Kopyalandı', copy: 'Kopya', currentValue: 'Mevcut değer', + error: 'Hata', goToSlide: (slide, count) => `${count} slayttan ${slide} slayta gidin`, hidePassword: 'Şifreyi sakla', loading: 'Yükleme', diff --git a/src/translations/zh-tw.ts b/src/translations/zh-tw.ts index 425c182af..6cb7a173e 100644 --- a/src/translations/zh-tw.ts +++ b/src/translations/zh-tw.ts @@ -9,8 +9,10 @@ const translation: Translation = { carousel: '旋轉木馬', clearEntry: '清空', close: '關閉', + copied: '已復制', copy: '複製', currentValue: '當前值', + error: '錯誤', goToSlide: (slide, count) => `轉到第 ${slide} 張幻燈片,共 ${count} 張`, hidePassword: '隱藏密碼', loading: '載入中', diff --git a/src/utilities/localize.ts b/src/utilities/localize.ts index 0b52443ab..9f22282eb 100644 --- a/src/utilities/localize.ts +++ b/src/utilities/localize.ts @@ -16,8 +16,10 @@ export interface Translation extends DefaultTranslation { carousel: string; clearEntry: string; close: string; + copied: string; copy: string; currentValue: string; + error: string; goToSlide: (slide: number, count: number) => string; hidePassword: string; loading: string;