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

Add language chooser package and component #64686

Open
wants to merge 49 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
d6cd0d9
Add language chooser package and component
swissspidy Aug 21, 2024
179fbfe
Add missing reference
swissspidy Aug 21, 2024
2418eb5
Merge branch 'trunk' into add/preferred-languages
swissspidy Aug 22, 2024
fdf9e08
Fix tests
swissspidy Aug 22, 2024
ec732c2
Fix tsconfig
swissspidy Aug 22, 2024
7148422
Make it work in core
swissspidy Aug 22, 2024
a9c0e17
Merge branch 'trunk' into add/preferred-languages
swissspidy Aug 22, 2024
86aa3a5
Minor fixes
swissspidy Aug 22, 2024
ed5b7c4
Remove mocks
swissspidy Aug 22, 2024
2027647
Move to subdirectory
swissspidy Aug 22, 2024
c26ef40
Manually handle keyboard shortcuts
swissspidy Aug 22, 2024
6fc4417
Move to `components` package
swissspidy Aug 23, 2024
5df3a8a
Merge branch 'trunk' into add/preferred-languages
swissspidy Aug 23, 2024
a166902
Fix relative imports
swissspidy Aug 23, 2024
4d9b3ff
Fix styling
swissspidy Aug 23, 2024
53f2477
Merge branch 'trunk' into add/preferred-languages
swissspidy Aug 23, 2024
2bf6711
Add changelog entry
swissspidy Aug 23, 2024
0a80a49
Merge branch 'trunk' into add/preferred-languages
swissspidy Aug 26, 2024
f91e7be
Move back to its own package
swissspidy Aug 26, 2024
2b86282
Undo changelog entry
swissspidy Aug 26, 2024
d4c35a9
Update lock file
swissspidy Aug 26, 2024
525a3d2
Und style change
swissspidy Aug 26, 2024
2d91af3
Update docs manifest
swissspidy Aug 26, 2024
35eceeb
Pass input name as prop
swissspidy Aug 27, 2024
e6623b8
Merge branch 'trunk' into add/preferred-languages
swissspidy Aug 27, 2024
fd761aa
Update test
swissspidy Aug 27, 2024
149b4d7
Merge branch 'trunk' into add/preferred-languages
swissspidy Aug 28, 2024
773381b
Fix storybook title
swissspidy Aug 28, 2024
469fa31
Fix css class names and file location
swissspidy Aug 28, 2024
6b9451c
Remove wp-clearfix
swissspidy Aug 28, 2024
770c92e
Use variables for colors
swissspidy Aug 28, 2024
7c758d5
Fix id matcher
swissspidy Aug 28, 2024
3069c89
Remove unused prop
swissspidy Aug 28, 2024
c433f6d
Add `onPreferredLanguagesChange` prop
swissspidy Aug 28, 2024
8c6c6cb
Remove errant space
swissspidy Aug 28, 2024
1928acc
Merge branch 'trunk' into add/preferred-languages
swissspidy Aug 30, 2024
d011bd7
Rename some props
swissspidy Aug 30, 2024
c53972b
Remove `HiddenFormField`
swissspidy Aug 30, 2024
5cfc625
Remove spinner
swissspidy Aug 30, 2024
60832b8
Use `ButtonGroup`
swissspidy Aug 30, 2024
e20ff2f
Remove old comment
swissspidy Aug 30, 2024
b041b25
Use `<Text>` component
swissspidy Aug 30, 2024
09a77d1
Fix before/after selection
swissspidy Aug 30, 2024
bfa3170
Expand translator comment
swissspidy Aug 30, 2024
177313d
Add comment
swissspidy Aug 30, 2024
5773726
Merge branch 'trunk' into add/preferred-languages
swissspidy Aug 30, 2024
71f8b6e
`defaultSelectedLanguages` vs `selectedLanguages`
swissspidy Aug 30, 2024
cd567e7
Merge branch 'trunk' into add/preferred-languages
swissspidy Sep 13, 2024
38f6331
Disable storybook keyboard shortcuts
swissspidy Sep 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1781,6 +1781,12 @@
"markdown_source": "../packages/keycodes/README.md",
"parent": "packages"
},
{
"title": "@wordpress/language-chooser",
"slug": "packages-language-chooser",
"markdown_source": "../packages/language-chooser/README.md",
"parent": "packages"
},
{
"title": "@wordpress/lazy-import",
"slug": "packages-lazy-import",
Expand Down
9 changes: 9 additions & 0 deletions lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,15 @@ function gutenberg_register_packages_styles( $styles ) {
$version
);
$styles->add_data( 'wp-preferences', 'rtl', 'replace' );

gutenberg_override_style(
$styles,
'wp-language-chooser',
gutenberg_url( 'build/language-chooser/style.css' ),
array( 'wp-components' ),
$version
);
$styles->add_data( 'wp-language-chooser', 'rtl', 'replace' );
}
add_action( 'wp_default_styles', 'gutenberg_register_packages_styles' );

Expand Down
35 changes: 35 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@wordpress/is-shallow-equal": "file:packages/is-shallow-equal",
"@wordpress/keyboard-shortcuts": "file:packages/keyboard-shortcuts",
"@wordpress/keycodes": "file:packages/keycodes",
"@wordpress/language-chooser": "file:packages/language-chooser",
"@wordpress/list-reusable-blocks": "file:packages/list-reusable-blocks",
"@wordpress/media-utils": "file:packages/media-utils",
"@wordpress/notices": "file:packages/notices",
Expand Down
1 change: 1 addition & 0 deletions packages/language-chooser/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock=false
7 changes: 7 additions & 0 deletions packages/language-chooser/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!-- Learn how to maintain this file at https://github.com/WordPress/gutenberg/tree/HEAD/packages#maintaining-changelogs. -->

## Unreleased

### New Features

- Initial public release.
23 changes: 23 additions & 0 deletions packages/language-chooser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Language Chooser

Package used for rendering a UI component for choosing preferred languages.

> This package is meant to be used only with WordPress core. Feel free to use it in your own project but please keep in mind that it might never get fully documented.

## Installation

Install the module

```bash
npm install @wordpress/language-chooser --save
```

_This package assumes that your code will run in an **ES2015+** environment. If you're using an environment that has limited or no support for such language features and APIs, you should include [the polyfill shipped in `@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default#polyfill) in your code._

## Contributing to this package

This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects.

To find out more about contributing to this package or Gutenberg as a whole, please read the project's main [contributor guide](https://github.com/WordPress/gutenberg/tree/HEAD/CONTRIBUTING.md).

<br /><br /><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>
45 changes: 45 additions & 0 deletions packages/language-chooser/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "@wordpress/language-chooser",
"version": "1.0.0-prerelease",
"description": "Component for choosing multiple preferred languages.",
"author": "The WordPress Contributors",
"license": "GPL-2.0-or-later",
"keywords": [
"wordpress",
"gutenberg",
"templates",
"reusable blocks"
],
"homepage": "https://github.com/WordPress/gutenberg/tree/HEAD/packages/language-chooser/README.md",
"repository": {
"type": "git",
"url": "https://github.com/WordPress/gutenberg.git",
"directory": "packages/language-chooser"
},
"bugs": {
"url": "https://github.com/WordPress/gutenberg/issues"
},
"engines": {
"node": ">=18.12.0",
"npm": ">=8.19.2"
},
"main": "build/index.js",
"module": "build-module/index.js",
"sideEffects": [
"build-style/**",
"src/**/*.scss"
],
"types": "build-types",
"dependencies": {
"@babel/runtime": "^7.16.0",
"@wordpress/a11y": "file:../a11y",
"@wordpress/components": "file:../components",
"@wordpress/compose": "file:../compose",
"@wordpress/element": "file:../element",
"@wordpress/i18n": "file:../i18n",
"@wordpress/keycodes": "file:../keycodes"
},
"publishConfig": {
"access": "public"
}
}
1 change: 1 addition & 0 deletions packages/language-chooser/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as LanguageChooser } from './language-chooser';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we export LanguageChooser as a private API at least initially, to give us time to test it and make it public once we feel good about it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this what semver is for?

Can't really make this a private API as this will need to be used in WP core itself, not in Gutenberg. Not really a place where we can or should use @wordpress/private-apis.

Copy link
Contributor

@ciampo ciampo Aug 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this what semver is for?

In short: yes, but WordPress and Gutenberg are different. Third-party devs using WordPress don't get to choose a package version for @wordpress/* packages, and get whatever version the WordPress release ships with.

This also implies that, given the backward-compat policy of the WordPress project, we should avoid introducing breaking changes regardless of semver.

If we could apply semver as it's intended, conversations like this one (in which we both participated) wouldn't be necessary either.

In practical terms: I understand the technical limitations to why you couldn't use @wordpress/private-apis. Mine is a recommendation as someone who deals daily with the constraints of the above-mentioned backwards-compat policy. The more you can future-proof the APIs and have robust testing early on, the better. Any breaking changes have the potential to create disruption. This may be less of a problem for a niche, high-level package like this one.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we can't use @wordpress/private-apis in core, what did you invision?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't initially consider that @wordpress/private-apis couldn't be used.

In case we wanted to work iteratively with follow-up PRs until we feel confident in exposing the component as a public API, an alternative could be not to publish any APIs for now, and manually enable it any time we need to test it WPCore. Not great, but it would do the trick in the meantime.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case we could just not publish the package to npm, no?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting private to true also helps with that: https://docs.npmjs.com/cli/v10/configuring-npm/package-json#private

92 changes: 92 additions & 0 deletions packages/language-chooser/src/language-chooser/active-controls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* WordPress dependencies
*/
import { __, sprintf } from '@wordpress/i18n';
import { Button, ButtonGroup } from '@wordpress/components';

interface ActiveControlsProps {
onMoveUp: () => void;
onMoveDown: () => void;
onRemove: () => void;
isMoveUpDisabled: boolean;
isMoveDownDisabled: boolean;
isRemoveDisabled: boolean;
}
function ActiveControls( {
onMoveUp,
onMoveDown,
onRemove,
isMoveUpDisabled,
isMoveDownDisabled,
isRemoveDisabled,
}: ActiveControlsProps ) {
return (
<ButtonGroup>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're actually in the process of deprecating ButtonGroup (context), so it would be better to replace it with another component (maybe a VStack or a simple vanilla HTML element).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course it is. I just switched to it after it was recommended to use it. Guess I‘ll switch to something else now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@swissspidy feel free to blame me for this one.

I recommended using ButtonGroup as a better alternative to an unordered list, but I did it before we decided that ButtonGroup would be deprecated.

<Button
variant="secondary"
showTooltip
aria-keyshortcuts="ArrowUp"
aria-label={ sprintf(
/* translators: accessibility text */
__( 'Move up (%s)' ),
/* translators: keyboard shortcut (Arrow Up) */
__( 'Up' )
) }
label={
/* translators: keyboard shortcut (Arrow Up) */
__( 'Up' )
}
disabled={ isMoveUpDisabled }
accessibleWhenDisabled
onClick={ onMoveUp }
__next40pxDefaultSize
>
{ __( 'Move Up' ) }
</Button>
<Button
variant="secondary"
showTooltip
aria-keyshortcuts="ArrowDown"
aria-label={ sprintf(
/* translators: accessibility text */
__( 'Move down (%s)' ),
/* translators: keyboard shortcut (Arrow Down) */
__( 'Down' )
) }
label={
/* translators: keyboard shortcut (Arrow Down) */
__( 'Down' )
}
disabled={ isMoveDownDisabled }
accessibleWhenDisabled
onClick={ onMoveDown }
__next40pxDefaultSize
>
{ __( 'Move Down' ) }
</Button>
<Button
variant="secondary"
showTooltip
aria-keyshortcuts="Delete"
aria-label={ sprintf(
/* translators: accessibility text */
__( 'Remove from list (%s)' ),
/* translators: keyboard shortcut (Delete / Backspace) */
__( 'Delete' )
) }
label={
/* translators: keyboard shortcut (Delete / Backspace) */
__( 'Delete' )
}
disabled={ isRemoveDisabled }
accessibleWhenDisabled
onClick={ onRemove }
__next40pxDefaultSize
>
{ __( 'Remove' ) }
</Button>
</ButtonGroup>
);
}

export default ActiveControls;
123 changes: 123 additions & 0 deletions packages/language-chooser/src/language-chooser/active-locales.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* WordPress dependencies
*/
import { useLayoutEffect, useRef } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { __experimentalText as Text } from '@wordpress/components';

/**
* Internal dependencies
*/
import type { Language } from './types';
import ActiveControls from './active-controls';

interface ActiveLocalesProps {
languages: Language[];
activeLanguage?: Language;
showOptionSiteDefault?: boolean;
setActiveLanguage: ( language: Language ) => void;
onMoveUp: () => void;
onMoveDown: () => void;
onRemove: () => void;
isEmpty: boolean;
isMoveUpDisabled: boolean;
isMoveDownDisabled: boolean;
isRemoveDisabled: boolean;
labelId: string;
}

export function ActiveLocales( {
languages,
showOptionSiteDefault = false,
activeLanguage,
setActiveLanguage,
onMoveUp,
onMoveDown,
onRemove,
isEmpty,
isMoveUpDisabled,
isMoveDownDisabled,
isRemoveDisabled,
labelId,
}: ActiveLocalesProps ) {
const listRef = useRef< HTMLUListElement | null >( null );

useLayoutEffect( () => {
const selectedEl = listRef.current?.querySelector(
'[aria-selected="true"]'
);

if ( ! selectedEl ) {
return;
}

selectedEl.scrollIntoView( {
behavior: 'smooth',
block: 'nearest',
} );
}, [ activeLanguage, languages ] );

const activeDescendant = isEmpty ? '' : activeLanguage?.locale;

const className = isEmpty
? 'language-chooser__active-locales-list language-chooser__active-locales-list--empty'
: 'language-chooser__active-locales-list';

let emptyMessage = sprintf(
/* translators: Used in language chooser, indicating fall back to the site's default language. %s: English (United States) */
__( 'Falling back to %s.' ),
'English (United States)'
);

if ( showOptionSiteDefault ) {
/* translators: Used in language chooser, indicating fall back to the site's default language. */
emptyMessage = __( 'Falling back to Site Default.' );
swissspidy marked this conversation as resolved.
Show resolved Hide resolved
}

return (
<div className="language-chooser__active-locales">
{ isEmpty && (
<div className="language-chooser__active-locales-empty-message">
swissspidy marked this conversation as resolved.
Show resolved Hide resolved
<Text>{ __( 'Nothing set.' ) }</Text>
<Text>{ emptyMessage }</Text>
</div>
) }
<ul
role="listbox"
aria-labelledby={ labelId }
tabIndex={ 0 }
aria-activedescendant={ activeDescendant }
className={ className }
ref={ listRef }
>
{ languages.map( ( language ) => {
const { locale, nativeName, lang } = language;
return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
<li
key={ locale }
role="option"
aria-selected={ locale === activeLanguage?.locale }
id={ locale }
lang={ lang }
className="language-chooser__active-locale"
onClick={ () => setActiveLanguage( language ) }
>
{ nativeName }
</li>
);
} ) }
</ul>
<ActiveControls
onMoveUp={ onMoveUp }
onMoveDown={ onMoveDown }
onRemove={ onRemove }
isMoveUpDisabled={ isMoveUpDisabled }
isMoveDownDisabled={ isMoveDownDisabled }
isRemoveDisabled={ isRemoveDisabled }
/>
</div>
);
}

export default ActiveLocales;
Loading
Loading