From 8c686ecab75615418c811b783006380115dde0d8 Mon Sep 17 00:00:00 2001 From: JC Franco Date: Wed, 19 Oct 2022 12:14:06 -0700 Subject: [PATCH] feat(rating): add built-in translations (#5437) * feat(rating): add built-in translations * set asset dir * add initial message load * add effectiveLocale watcher * fix outdated reference * update lang switch helper to account for strings used in rendering * feat(time-picker, input-time-picker): add numberingSystem property (#5301) * feat(time-picker, input-time-picker): add numberingSystem property for localization * wip: need to correctly apply numberingSystem on InputInput * use NumberStringFormat to delocalize time * cleanup * delocalize time on blur * test(input-time-picker): fix e2e failures and add numberingSystem story * use formatter's format method instead of localize for performance * cleanup * fix(input-time-picker): don't pass through lang and numberingSystem to calcite-input * fix test * use NumberStringFormat's setter instead of setOptions Co-authored-by: Ben Elan * refactor(block): move hardcoded classes to CSS object (#5474) * fix(input, input-number, input-text): fix input icons not displaying properly in Firefox (#5475) #5417 * ci(next): clean docs build before deployment (#5476) * ci(prepReleaseCommit): skip git sanity checks for next releases * ci(next): clean docs build before deployment * Revert "ci(prepReleaseCommit): skip git sanity checks for next releases" This reverts commit 7cc2d8b2ea8a75bbf85258f1309ab85d5581e075. Co-authored-by: Ben Elan * refactor(input-number, input-text): drop resize-icon selectors (#5477) * fix(block): improve content layout (#5473) #5422 * docs: update component READMEs (#5479) Co-authored-by: jcfranco * docs: add backticks to props and values for consistency (#5485) * docs: add backticks to props and values for consistency * update prop ref * review edits * fix(date-picker): no longer hides year for zh-CN locale (#5344) * fix(date-picker): no longer hides year for zn-CH locale * add screener story * fix locale typo * fix suffix placement * docs(changelog): add missing breaking changes (#5469) * feat(stepper, stepper-item): add numberingSystem property (#5467) * feat(stepper, stepper-item): add numberingSystem property * use setter for NumberStringFormat's options instead of setOptions * change formatter options in effectiveLocale watcher * cleanup Co-authored-by: Ben Elan * fix(flow-item): fix scrollContentTo (#5487) #5414 * ci(next): fix next releases take 3 (#5497) * ci(next): fix next releases take 3 * cleanup Co-authored-by: Ben Elan * fix(flow-item, panel): fix layout issue that would cause double scrollbars (#5486) #5428 * 1.0.0-next.598 * docs: update component READMEs (#5501) Co-authored-by: jcfranco * build(deps): bump eslint-plugin-jest from 27.1.1 to 27.1.2 (#5507) Bumps [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) from 27.1.1 to 27.1.2. - [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases) - [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md) - [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v27.1.1...v27.1.2) --- updated-dependencies: - dependency-name: eslint-plugin-jest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump shell-quote from 1.7.3 to 1.7.4 (#5503) Bumps [shell-quote](https://github.com/ljharb/shell-quote) from 1.7.3 to 1.7.4. - [Release notes](https://github.com/ljharb/shell-quote/releases) - [Changelog](https://github.com/ljharb/shell-quote/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/shell-quote/compare/1.7.3...v1.7.4) --- updated-dependencies: - dependency-name: shell-quote dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @typescript-eslint/eslint-plugin from 5.38.1 to 5.40.1 (#5506) Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 5.38.1 to 5.40.1. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.40.1/packages/eslint-plugin) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump @typescript-eslint/parser from 5.38.1 to 5.40.1 (#5504) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 5.38.1 to 5.40.1. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v5.40.1/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * build(deps): bump eslint-plugin-jsdoc from 39.3.6 to 39.3.13 (#5505) Bumps [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) from 39.3.6 to 39.3.13. - [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases) - [Commits](https://github.com/gajus/eslint-plugin-jsdoc/compare/v39.3.6...v39.3.13) --- updated-dependencies: - dependency-name: eslint-plugin-jsdoc dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(tree, tree-item): works when tree is the topmost element in a shadow root where it has no parent (#5472) #5333 * ci: fix next releases take 4 (#5500) * ci: fix next releases take 4 * separate error messages Co-authored-by: Ben Elan * 1.0.0-next.599 * fix(flow-item): Render back button first. (#5511) * 1.0.0-next.600 * fix(tab): applies section styles onto the enclosing parent (#5516) * WIP: temorary demo swap with the issue case * fix(tab): applies section styles onto the enclosing parent * revert to original demo * cleanup * TabChilrenWithPercentageHeights story * cleanup * swap parent selector for [role=tabpanel] * Introduce a new class selector * 1.0.0-next.601 Signed-off-by: dependabot[bot] Co-authored-by: Ben Elan Co-authored-by: Ben Elan Co-authored-by: Calcite Admin Co-authored-by: jcfranco Co-authored-by: Kitty Hurley Co-authored-by: Anveshreddy mekala Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Eliza Khachatryan Co-authored-by: github-actions[bot] Co-authored-by: Matt Driscoll --- src/components/rating/rating.e2e.ts | 13 +++- src/components/rating/rating.tsx | 114 ++++++++++++++++++++-------- src/components/rating/resources.ts | 4 - src/tests/commonTests.ts | 34 ++++++--- 4 files changed, 118 insertions(+), 47 deletions(-) delete mode 100644 src/components/rating/resources.ts diff --git a/src/components/rating/rating.e2e.ts b/src/components/rating/rating.e2e.ts index 1109a299c76..8d322e041f0 100644 --- a/src/components/rating/rating.e2e.ts +++ b/src/components/rating/rating.e2e.ts @@ -1,5 +1,14 @@ import { newE2EPage } from "@stencil/core/testing"; -import { renders, accessible, focusable, labelable, formAssociated, disabled, hidden } from "../../tests/commonTests"; +import { + renders, + accessible, + focusable, + labelable, + formAssociated, + disabled, + hidden, + t9n +} from "../../tests/commonTests"; describe("calcite-rating", () => { it("renders", async () => renders("", { display: "flex" })); @@ -12,6 +21,8 @@ describe("calcite-rating", () => { it("can be disabled", () => disabled("")); + it("supports translations", () => t9n("calcite-rating")); + it("renders outlined star when no value or average is set", async () => { const page = await newE2EPage(); await page.setContent(""); diff --git a/src/components/rating/rating.tsx b/src/components/rating/rating.tsx index 0c4f44d8447..334b62eeb77 100644 --- a/src/components/rating/rating.tsx +++ b/src/components/rating/rating.tsx @@ -9,22 +9,39 @@ import { Method, Prop, State, - VNode + VNode, + Watch } from "@stencil/core"; import { guid } from "../../utils/guid"; import { Scale } from "../interfaces"; import { LabelableComponent, connectLabel, disconnectLabel } from "../../utils/label"; import { connectForm, disconnectForm, FormComponent, HiddenFormInputSlot } from "../../utils/form"; -import { TEXT } from "./resources"; import { InteractiveComponent, updateHostInteraction } from "../../utils/interactive"; import { isActivationKey } from "../../utils/key"; +import { connectLocalized, disconnectLocalized, LocalizedComponent } from "../../utils/locale"; +import { + connectMessages, + disconnectMessages, + setUpMessages, + T9nComponent, + updateMessages +} from "../../utils/t9n"; +import { Messages } from "./assets/rating/t9n"; @Component({ tag: "calcite-rating", styleUrl: "rating.scss", - shadow: true + shadow: true, + assetsDirs: ["assets"] }) -export class Rating implements LabelableComponent, FormComponent, InteractiveComponent { +export class Rating + implements + LabelableComponent, + FormComponent, + InteractiveComponent, + LocalizedComponent, + T9nComponent +{ //-------------------------------------------------------------------------- // // Element @@ -39,43 +56,54 @@ export class Rating implements LabelableComponent, FormComponent, InteractiveCom // // -------------------------------------------------------------------------- - /** Specifies the size of the component. */ - @Prop({ reflect: true }) scale: Scale = "m"; - - /** The component's value. */ - @Prop({ reflect: true, mutable: true }) value = 0; - - /** When `true`, the component's value can be read, but cannot be modified. */ - @Prop({ reflect: true }) readOnly = false; - - /** When `true`, interaction is prevented and the component is displayed with lower opacity. */ - @Prop({ reflect: true }) disabled = false; - - /** When `true`, and if available, displays the `average` and/or `count` data summary in a `calcite-chip`. */ - @Prop({ reflect: true }) showChip = false; + /** Specifies a cumulative average from previous ratings to display. */ + @Prop({ reflect: true }) average?: number; /** Specifies the number of previous ratings to display. */ @Prop({ reflect: true }) count?: number; - /** Specifies a cumulative average from previous ratings to display. */ - @Prop({ reflect: true }) average?: number; - - /** Specifies the name of the component on form submission. */ - @Prop({ reflect: true }) name: string; + /** When `true`, interaction is prevented and the component is displayed with lower opacity. */ + @Prop({ reflect: true }) disabled = false; /** * Accessible name for the component. * - * @default "Rating" + * @deprecated – translations are now built-in, if you need to override a string, please use `messageOverrides` */ - @Prop() intlRating?: string = TEXT.rating; + @Prop() intlRating: string; /** * Accessible name for each star. The `${num}` in the string will be replaced by the number. * - * @default "Stars: ${num}" + * @deprecated – translations are now built-in, if you need to override a string, please use `messageOverrides` + */ + @Prop() intlStars: string; + + /** + * Made into a prop for testing purposes only + * + * @internal */ - @Prop() intlStars?: string = TEXT.stars; + @Prop({ mutable: true }) messages: Messages; + + /** + * Use this property to override individual strings used by the component. + */ + @Prop({ mutable: true }) messageOverrides: Partial; + + @Watch("intlRating") + @Watch("intlStars") + @Watch("defaultMessages") + @Watch("messageOverrides") + onMessagesChange(): void { + /* wired up by t9n util */ + } + + /** Specifies the name of the component on form submission. */ + @Prop({ reflect: true }) name: string; + + /** When `true`, the component's value can be read, but cannot be modified. */ + @Prop({ reflect: true }) readOnly = false; /** * When `true`, the component must have a value in order for the form to submit. @@ -84,6 +112,15 @@ export class Rating implements LabelableComponent, FormComponent, InteractiveCom */ @Prop({ reflect: true }) required = false; + /** Specifies the size of the component. */ + @Prop({ reflect: true }) scale: Scale = "m"; + + /** When `true`, and if available, displays the `average` and/or `count` data summary in a `calcite-chip`. */ + @Prop({ reflect: true }) showChip = false; + + /** The component's value. */ + @Prop({ reflect: true, mutable: true }) value = 0; + //-------------------------------------------------------------------------- // // Lifecycle @@ -91,11 +128,19 @@ export class Rating implements LabelableComponent, FormComponent, InteractiveCom //-------------------------------------------------------------------------- connectedCallback(): void { + connectLocalized(this); + connectMessages(this); connectLabel(this); connectForm(this); } + async componentWillLoad(): Promise { + await setUpMessages(this); + } + disconnectedCallback(): void { + disconnectLocalized(this); + disconnectMessages(this); disconnectLabel(this); disconnectForm(this); } @@ -160,7 +205,7 @@ export class Rating implements LabelableComponent, FormComponent, InteractiveCom )} - {this.intlStars.replace("${num}", `${i}`)} + {this.messages.stars.replace("${num}", `${i}`)} @@ -198,7 +243,7 @@ export class Rating implements LabelableComponent, FormComponent, InteractiveCom onPointerLeave={() => (this.hoverValue = null)} onTouchEnd={() => (this.hoverValue = null)} > - {intlRating} + {messages.rating} {this.renderStars()} {(count || average) && showChip ? ( @@ -267,6 +312,15 @@ export class Rating implements LabelableComponent, FormComponent, InteractiveCom defaultValue: Rating["value"]; + @State() effectiveLocale = ""; + + @Watch("effectiveLocale") + effectiveLocaleChange(): void { + updateMessages(this, this.effectiveLocale); + } + + @State() defaultMessages: Messages; + @State() hoverValue: number; @State() focusValue: number; diff --git a/src/components/rating/resources.ts b/src/components/rating/resources.ts deleted file mode 100644 index 45b731ae629..00000000000 --- a/src/components/rating/resources.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const TEXT = { - rating: "Rating", - stars: "Stars: ${num}" -}; diff --git a/src/tests/commonTests.ts b/src/tests/commonTests.ts index 5bc09f2bfc3..a61756b07f4 100644 --- a/src/tests/commonTests.ts +++ b/src/tests/commonTests.ts @@ -981,25 +981,35 @@ export async function t9n( } async function assertLangSwitch(): Promise { - await page.evaluate(() => { - const orig = window.fetch; - window.fetch = async function (input, init) { - if (typeof input === "string" && input.endsWith("messages_es.json")) { - const dummyMessages = {}; - window.fetch = orig; - return new Response(new Blob([JSON.stringify(dummyMessages, null, 2)], { type: "application/json" })); - } + const enMessages = await getCurrentMessages(); + const fakeBundleIdentifier = "__fake__"; + await page.evaluate( + (enMessages, fakeBundleIdentifier) => { + const orig = window.fetch; + window.fetch = async function (input, init) { + if (typeof input === "string" && input.endsWith("messages_es.json")) { + const fakeEsMessages = { + ...enMessages, // reuse real message bundle in case component rendering depends on strings + + [fakeBundleIdentifier]: true // we inject a fake identifier for assertion-purposes + }; + window.fetch = orig; + return new Response(new Blob([JSON.stringify(fakeEsMessages, null, 2)], { type: "application/json" })); + } - return orig.call(input, init); - }; - }); + return orig.call(input, init); + }; + }, + enMessages, + fakeBundleIdentifier + ); component.setAttribute("lang", "es"); await page.waitForChanges(); await page.waitForTimeout(3000); const esMessages = await getCurrentMessages(); - expect(esMessages).toEqual({}); + expect(esMessages).toHaveProperty(fakeBundleIdentifier); // reset test changes component.removeAttribute("lang");