-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
base: trunk
Are you sure you want to change the base?
Changes from all commits
d6cd0d9
179fbfe
2418eb5
fdf9e08
ec732c2
7148422
a9c0e17
86aa3a5
ed5b7c4
2027647
c26ef40
6fc4417
5df3a8a
a166902
4d9b3ff
53f2477
2bf6711
0a80a49
f91e7be
2b86282
d4c35a9
525a3d2
2d91af3
35eceeb
e6623b8
fd761aa
149b4d7
773381b
469fa31
6b9451c
770c92e
7c758d5
3069c89
c433f6d
8c6c6cb
1928acc
d011bd7
c53972b
5cfc625
60832b8
e20ff2f
b041b25
09a77d1
bfa3170
177313d
5773726
71f8b6e
cd567e7
38f6331
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
package-lock=false |
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. |
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> |
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" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default as LanguageChooser } from './language-chooser'; | ||
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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're actually in the process of deprecating There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @swissspidy feel free to blame me for this one. I recommended using |
||
<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; |
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; |
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.There was a problem hiding this comment.
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?There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting
private
totrue
also helps with that: https://docs.npmjs.com/cli/v10/configuring-npm/package-json#private