Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚀 Next release #1147

Merged
merged 28 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5637224
feat(DatatableV2): allow localization of component
ajkl2533 Sep 25, 2024
29925b7
refactor(Select): replace defaultProps property
silvioprog Sep 26, 2024
22f9125
Merge pull request #1141 from securityscorecard/ajkl2533@UXD-1629
ajkl2533 Sep 26, 2024
f8795d1
Merge pull request #1146 from securityscorecard/silvioprog@UXD-1632
silvioprog Sep 26, 2024
8f4aaf4
refactor(Center): replace defaultProps property
silvioprog Sep 26, 2024
c8e2095
refactor(import): remove default React imports
ajkl2533 Sep 26, 2024
542206e
Merge pull request #1143 from securityscorecard/ajkl2533@UXD-1572
ajkl2533 Sep 30, 2024
992033c
Merge pull request #1148 from securityscorecard/silvioprog@UXD-1633
silvioprog Sep 30, 2024
145073e
refactor(Cluster): replace defaultProps property
silvioprog Sep 30, 2024
cc44a37
refactor(Grid): replace defaultProps property
silvioprog Sep 30, 2024
b3dbbdb
refactor(Inline): replace defaultProps property
silvioprog Sep 30, 2024
6eebb27
refactor(Padbox): replace defaultProps property
silvioprog Sep 30, 2024
1092b9e
refactor(Paragraph): replace defaultProps property
silvioprog Sep 30, 2024
6237566
refactor(Spinner): replace defaultProps property
silvioprog Sep 30, 2024
fbebad9
refactor(Text): replace defaultProps property
silvioprog Sep 30, 2024
7ebfefe
fix(DatatableV2): update column settings appearance
ajkl2533 Oct 1, 2024
6b96315
Merge pull request #1150 from securityscorecard/silvioprog@UXD-1635
silvioprog Oct 1, 2024
e4977a1
Merge pull request #1151 from securityscorecard/silvioprog@UXD-1636
silvioprog Oct 1, 2024
c956faf
Merge pull request #1152 from securityscorecard/silvioprog@UXD-1637
silvioprog Oct 1, 2024
61dba54
Merge pull request #1153 from securityscorecard/silvioprog@UXD-1638
silvioprog Oct 1, 2024
c625ced
Merge pull request #1154 from securityscorecard/silvioprog@UXD-1639
silvioprog Oct 1, 2024
9181ca8
Merge pull request #1149 from securityscorecard/silvioprog@UXD-1634
silvioprog Oct 1, 2024
6578ffb
Merge pull request #1155 from securityscorecard/silvioprog@UXD-1640
ajkl2533 Oct 2, 2024
6c89b93
Merge pull request #1156 from securityscorecard/ajkl2533@UXD-1641
ajkl2533 Oct 2, 2024
c034c7b
fix(DatatableV2): use skeleton for rows counters
ajkl2533 Oct 2, 2024
c3d0ab6
Merge pull request #1157 from securityscorecard/ajkl2533@UXD-1642
ajkl2533 Oct 2, 2024
15a4896
feat(i18n): switch to ICU message format syntax
ajkl2533 Oct 2, 2024
db9361a
Merge pull request #1158 from securityscorecard/ajkl2533@UXD-1629-2
ajkl2533 Oct 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 4 additions & 4 deletions .storybook/blocks/FontPallete.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { FunctionComponent } from 'react';
import { ReactNode } from 'react';
import styled from 'styled-components';

import { theme } from '../../src/theme';
Expand Down Expand Up @@ -73,15 +73,15 @@ interface FontProps {
sampleText: string;
}

export const FontItem: FunctionComponent<FontProps> = ({
export const FontItem = ({
title,
subtitle,
fontFamily = theme.typography.family.base,
fontWeight = theme.typography.weight.regular,
fontSize = theme.typography.size.lg,
lineHeight = 'normal',
sampleText,
}) => {
}:FontProps) => {
return (
<Item>
<ItemDescription>
Expand All @@ -100,7 +100,7 @@ export const FontItem: FunctionComponent<FontProps> = ({
);
};

export const FontPalette: FunctionComponent = ({ children, ...props }) => (
export const FontPalette = ({ children, ...props }: {children: ReactNode}) => (
<List {...props}>
<ListHeading>
<ListName>Name</ListName>
Expand Down
1 change: 0 additions & 1 deletion .storybook/blocks/SpaceScale.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import { endsWith } from 'ramda'

import { Table, TableBody, TableHead, Token } from "./components"
Expand Down
6 changes: 0 additions & 6 deletions .storybook/decorators/withThemeByClassName.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,12 @@ import type { StoryContext } from 'storybook/internal/types';
const PARAM_KEY = 'themes' as const;
const ADDON_ID = `storybook/${PARAM_KEY}` as const;
const GLOBAL_KEY = 'theme' as const;
const THEME_SWITCHER_ID = `${ADDON_ID}/theme-switcher` as const;

interface ThemeAddonState {
themesList: string[];
themeDefault?: string;
}

const DEFAULT_ADDON_STATE: ThemeAddonState = {
themesList: [],
themeDefault: undefined,
};

interface ThemeParameters {
themeOverride?: string;
disable?: boolean;
Expand Down
Binary file modified .storybook/image-snapshots/expected/Iconography_List_List.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 13 additions & 2 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import React, { useEffect } from 'react';
import { useEffect } from 'react';
import type { Preview } from '@storybook/react';
import { withScreenshot } from 'storycap';
import i18n from 'i18next';
import { initReactI18next, I18nextProvider } from 'react-i18next';
import ICU from 'i18next-icu';
import icuEn from 'i18next-icu/locale-data/en';
import icuJa from 'i18next-icu/locale-data/ja';
import icuEs from 'i18next-icu/locale-data/es';
import icuPt from 'i18next-icu/locale-data/pt';
import icuCs from 'i18next-icu/locale-data/cs';

import { DSProvider, createIconLibrary } from '../src/theme';
import en from '../src/locales/en-US';
Expand All @@ -18,6 +24,7 @@ import '@fontsource/inter/600.css';
import '@fontsource/inter/700.css';
import '@fontsource/space-mono/400.css';
import '../src/tokens/tokens.css';
import { SlowBuffer } from 'buffer';

function clearDatatableLS() {
Object.keys(localStorage)
Expand All @@ -28,8 +35,12 @@ clearDatatableLS();
createIconLibrary();
window.Math.random = () => 0.5;

i18n.use(initReactI18next).init({
i18n.use(ICU).use(initReactI18next).init({
debug: true,
i18nFormat: {
localeData: [icuEn, icuJa, icuEs, icuPt, icuCs],
formats: {},
},
resources: {
'en-US': { sscds: en },
'ja-JP': { sscds: ja },
Expand Down
62 changes: 62 additions & 0 deletions docs/localization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,68 @@ We've chosen to implement localization using `i18next` and `react-i18next`. This

`i18next` provides a robust framework for managing translations, while `react-i18next` offers React-specific bindings that make it simple to use localized content within our components. This approach allows for dynamic language switching and efficient management of translation resources.

## String interpolation

Our localization system utilizes the ICU (International Components for Unicode) Message Format instead of the default i18next syntax for string interpolation and pluralization. This format provides a more powerful and standardized way to handle complex localization scenarios.

### What is ICU Message Format?

ICU Message Format is a standardized localization format used widely in software internationalization. It allows for sophisticated string formatting, including pluralization, gender, and selectional mechanisms. This format is more expressive than simple key-value pairs and can handle a wide variety of linguistic rules across different languages.

### How It Works

ICU Message Format uses placeholders and formatting instructions within curly braces {}. These placeholders can be simple variables, or they can include formatting options for numbers, dates, and pluralization.

### Syntax Examples

#### Basic String Interpolation

To insert a variable into a string, use the variable name within curly braces:

```
"Hello, {name}!"
```

When providing the translation, you would use:

```json
{
"greeting": "Hello, {name}!"
}
```

And in your code:

```js
t('greeting', { name: 'Alice' });
```

This would result in: "Hello, Alice!"

#### Pluralization

For pluralization, use the plural keyword followed by the variable and the different forms:

```
"{count, plural, =0 {No items} one {{count} item} other {{count} items}}"
```

In your translation file:

```json
{
"itemCount": "{count, plural, =0 {No items} one {{count} item} other {{count} items}}"
}
```

In your code:

```js
t('itemCount', { count: 0 }); // "No items"
t('itemCount', { count: 1 }); // "1 item"
t('itemCount', { count: 5 }); // "5 items"
```

## Integration into Existing Apps

Integrating our localized components into your existing application is straightforward. Here are the steps:
Expand Down
40 changes: 27 additions & 13 deletions jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,37 @@ import '@testing-library/jest-dom/extend-expect';
import 'jest-styled-components';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import ICU from 'i18next-icu';
import icuEn from 'i18next-icu/locale-data/en';
import icuJa from 'i18next-icu/locale-data/ja';
import icuEs from 'i18next-icu/locale-data/es';
import icuPt from 'i18next-icu/locale-data/pt';
import icuCs from 'i18next-icu/locale-data/cs';

import { createIconLibrary, resetIconLibrary } from './src';
import en from './src/locales/en-US';

i18n.use(initReactI18next).init({
resources: {
'en-US': { sscds: en },
},
defaultNS: 'sscds',
keySeparator: false,
nsSeparator: '|',
lng: 'en-US',
fallbackLng: 'en-US',
interpolation: {
escapeValue: false,
},
});
i18n
.use(ICU)
.use(initReactI18next)
.init({
debug: true,
i18nFormat: {
localeData: [icuEn, icuJa, icuEs, icuPt, icuCs],
formats: {},
},
resources: {
'en-US': { sscds: en },
},
defaultNS: 'sscds',
keySeparator: false,
nsSeparator: '|',
lng: 'en-US',
fallbackLng: 'en-US',
interpolation: {
escapeValue: false,
},
});

Object.defineProperty(document, 'fonts', {
value: {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"dedent": "^1.5.1",
"fast-deep-equal": "^3.1.3",
"i18next": ">=17.3.1",
"i18next-icu": "^1.4.2",
"numeral": "^2.0.6",
"pullstate": "^1.23.0",
"ramda": "^0.28.0",
Expand Down
6 changes: 3 additions & 3 deletions src/components/Banner/Banner.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo } from 'react';
import { type MouseEvent, useMemo } from 'react';
import styled from 'styled-components';
import { isNonEmptyArray, noop } from 'ramda-adjunct';
import cls from 'classnames';
Expand Down Expand Up @@ -50,10 +50,10 @@ const BannerContent = ({ children, actions, isInline }: BannerContentProps) => (
{actions.map((action) => (
<Button
key={action.name}
href={(action as AbsoluteLinkActionKind<[React.MouseEvent]>).href}
href={(action as AbsoluteLinkActionKind<[MouseEvent]>).href}
name={action.name}
style={{ height: '2rem' }}
to={(action as RelativeLinkActionKind<[React.MouseEvent]>).to}
to={(action as RelativeLinkActionKind<[MouseEvent]>).to}
variant="outline"
onClick={action.onClick}
>
Expand Down
8 changes: 4 additions & 4 deletions src/components/Banner/Banner.types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React, { ReactNode } from 'react';
import { type MouseEvent, type MouseEventHandler, ReactNode } from 'react';

import { BaseToastBannerProps } from '../_internal/BaseToastBanner/BaseToastBanner.types';
import { ActionKinds } from '../../types/action.types';
import { BannerVariants } from './Banner.enums';

export type ActionsArray = readonly [
ActionKinds<[React.MouseEvent]>?,
ActionKinds<[React.MouseEvent]>?,
ActionKinds<[MouseEvent]>?,
ActionKinds<[MouseEvent]>?,
];

type Variants = (typeof BannerVariants)[keyof typeof BannerVariants];
Expand All @@ -15,7 +15,7 @@ export type BannerProps = {
/** Toggles display of the close button */
isDismissable?: boolean;
/** Callback triggered on close button click */
onClose?: React.MouseEventHandler;
onClose?: MouseEventHandler;
/** Banner container width in which action buttons will switch the layout from inline to block */
changeLayoutBreakpoint?: number;
className?: string;
Expand Down
1 change: 0 additions & 1 deletion src/components/Breadcrumbs/Breadcrumbs.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import { Meta, StoryFn } from '@storybook/react';
import { MemoryRouter } from 'react-router-dom';

Expand Down
1 change: 0 additions & 1 deletion src/components/Breadcrumbs/Breadcrumbs.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { fireEvent, screen, waitFor } from '@testing-library/react';
import React from 'react';

import { renderWithProviders } from '../../utils/tests/renderWithProviders';
import BreadcrumbItem from './BreadcrumbItem';
Expand Down
40 changes: 22 additions & 18 deletions src/components/Breadcrumbs/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import * as React from 'react';
import { slice } from 'ramda';
import styled from 'styled-components';
import { isNilOrEmpty, isNotNilOrEmpty } from 'ramda-adjunct';
import cls from 'classnames';
import {
Children,
type MouseEvent,
type ReactElement,
type ReactNode,
cloneElement,
isValidElement,
} from 'react';

import type {
BreadcrumbItemProps,
Expand Down Expand Up @@ -31,7 +38,7 @@ const itemsAfterCollapse = 3;
const itemsBeforeCollapse = 1;

// Build list of breadcrumbs interspersing a separator
const insertSeparators = (items: React.ReactElement[]) => {
const insertSeparators = (items: ReactElement[]) => {
return items.reduce((prev, current, index) => {
if (index < items.length - 1) {
return [
Expand All @@ -52,7 +59,7 @@ const insertSeparators = (items: React.ReactElement[]) => {
}, []);
};

const renderDropdown = (actions: ActionKinds<React.MouseEvent[]>[]) => (
const renderDropdown = (actions: ActionKinds<MouseEvent[]>[]) => (
<li key="breadcrumbs-dropdown">
<DropdownMenu
actions={actions}
Expand All @@ -69,8 +76,8 @@ const renderDropdown = (actions: ActionKinds<React.MouseEvent[]>[]) => (

// this renders the list of items only when the count of the actions is bigger than 2
const renderItemsBeforeAndAfter = (
allItems: React.ReactNode[],
allDropdownActions: ActionKinds<React.MouseEvent[]>[],
allItems: ReactNode[],
allDropdownActions: ActionKinds<MouseEvent[]>[],
) => {
const dropdown = renderDropdown(allDropdownActions);
return [
Expand All @@ -81,28 +88,25 @@ const renderItemsBeforeAndAfter = (
};

const Breadcrumbs = ({ children, className, ...props }: BreadcrumbsProps) => {
const allItems = React.Children.map(children, (breadcrumbItem) => {
if (!React.isValidElement(breadcrumbItem)) {
const allItems = Children.map(children, (breadcrumbItem) => {
if (!isValidElement(breadcrumbItem)) {
return null;
}

return React.cloneElement(
breadcrumbItem as React.ReactElement<BreadcrumbItemProps>,
{
isSelected:
isNilOrEmpty(breadcrumbItem.props.to) &&
isNilOrEmpty(breadcrumbItem.props.href),
...props,
},
);
return cloneElement(breadcrumbItem as ReactElement<BreadcrumbItemProps>, {
isSelected:
isNilOrEmpty(breadcrumbItem.props.to) &&
isNilOrEmpty(breadcrumbItem.props.href),
...props,
});
});

const allDropdownActions = slice(
itemsBeforeCollapse,
-Math.abs(itemsAfterCollapse),
)(
React.Children.toArray(children).map((breadcrumbItem) => {
if (!React.isValidElement(breadcrumbItem)) {
Children.toArray(children).map((breadcrumbItem) => {
if (!isValidElement(breadcrumbItem)) {
return null;
}
return {
Expand Down
1 change: 0 additions & 1 deletion src/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import { Meta, StoryFn } from '@storybook/react';

import { SSCIconNames } from '../../theme/icons/icons.enums';
Expand Down
4 changes: 2 additions & 2 deletions src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import { forwardRef } from 'react';
import styled, { css } from 'styled-components';
import cls from 'classnames';

Expand Down Expand Up @@ -46,7 +46,7 @@ export const CardContainer = styled.div<{
`var(--sscds-space-${$verticalPadding}) var(--sscds-space-${$horizontalPadding})`};
`;

const Card = React.forwardRef<HTMLDivElement, CardProps>(
const Card = forwardRef<HTMLDivElement, CardProps>(
(
{ children, shouldAlignLastItemToBottom = false, as, ...props }: CardProps,
ref,
Expand Down
Loading
Loading