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

Try to detect language from html lang attributes and navigator.languages #38

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
26 changes: 13 additions & 13 deletions packages/griffith/README-zh-Hans.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ render(<Player {...props} />)

### `props`

| 字段 | 类型 | 默认值 | 说明 |
| --------------------- | ------------------------------------------------- | --------- | ------------------------------------------ |
| `id` | `string` | | 播放器实例唯一标识 |
| `title` | `string` | | 视频标题 |
| `cover` | `string` | | 视频封面图片 URL |
| `duration` | `number` | | 初始视频时长。在视频元数据载入后使用实际值 |
| `sources` | `sources` | | 视频播放数据。具体见下, |
| `standalone` | `boolean` | `false` | 是否启用 standalone 模式 |
| `onBeforePlay` | `function` | `void` | 视频播放之前回调函数 |
| `shouldObserveResize` | `boolean` | `false` | 是否监听窗口 resize |
| `initialObjectFit` | `fill \| \contain \| cover \| none \| scale-down` | `contain` | object-fit 参数 |
| `useMSE` | `boolean` | `false` | 是否启用 MSE |
| `locale` | `en \| ja \| zh-Hans \| zh-Hant` | `en` | 界面语言 |
| 字段 | 类型 | 默认值 | 说明 |
| --------------------- | ------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------- |
| `id` | `string` | | 播放器实例唯一标识 |
| `title` | `string` | | 视频标题 |
| `cover` | `string` | | 视频封面图片 URL |
| `duration` | `number` | | 初始视频时长。在视频元数据载入后使用实际值 |
| `sources` | `sources` | | 视频播放数据。具体见下, |
| `standalone` | `boolean` | `false` | 是否启用 standalone 模式 |
| `onBeforePlay` | `function` | `void` | 视频播放之前回调函数 |
| `shouldObserveResize` | `boolean` | `false` | 是否监听窗口 resize |
| `initialObjectFit` | `fill \| \contain \| cover \| none \| scale-down` | `contain` | object-fit 参数 |
| `useMSE` | `boolean` | `false` | 是否启用 MSE |
| `language` | `en \| ja \| zh-Hans \| zh-Hant` | `en` | 界面语言。如果未指定,会根据 `html` 标签的 `lang` 属性以及 `navigator.languages` 匹配,如果没有匹配默认为 `en` |

`sources` 字段:

Expand Down
26 changes: 13 additions & 13 deletions packages/griffith/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,19 @@ render(<Player {...props} />)

### `Props`

| Name | Type | Default | Description |
| --------------------- | ------------------------------------------------ | --------- | ------------------------------------------------------------------------ |
| `id` | `string` | | Unique identifier of the player instance |
| `title` | `string` | | Video title |
| `cover` | `string` | | Video cover image |
| `duration` | `number` | | Initial video duration. Use actual values after video metadata is loaded |
| `sources` | `sources` | | Video playback data |
| `standalone` | `boolean` | `false` | Enable standalone mode |
| `onBeforePlay` | `function` | `void` | Callback function before video playback |
| `shouldObserveResize` | `boolean` | `false` | Listen to the window resize |
| `initialObjectFit` | `fill \| contain \| cover \| none \| scale-down` | `contain` | object-fit |
| `useMSE` | `boolean` | `false` | Enable Media Source Extensions™ |
| `locale` | `en \| ja \| zh-Hans \| zh-Hant` | `en` | UI Locale |
| Name | Type | Default | Description |
| --------------------- | ------------------------------------------------ | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | `string` | | Unique identifier of the player instance |
| `title` | `string` | | Video title |
| `cover` | `string` | | Video cover image |
| `duration` | `number` | | Initial video duration. Use actual values after video metadata is loaded |
| `sources` | `sources` | | Video playback data |
| `standalone` | `boolean` | `false` | Enable standalone mode |
| `onBeforePlay` | `function` | `void` | Callback function before video playback |
| `shouldObserveResize` | `boolean` | `false` | Listen to the window resize |
| `initialObjectFit` | `fill \| contain \| cover \| none \| scale-down` | `contain` | object-fit |
| `useMSE` | `boolean` | `false` | Enable Media Source Extensions™ |
| `language` | `en \| ja \| zh-Hans \| zh-Hant` | | UI language. If not specified, Griffith will try to detect from `html`'s `lang` attribute and `navigator.languages` and eventually fallback to `en`. |

`sources`:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import {MessageProvider, InternalContext} from '../../contexts/Message'
import {PositionProvider} from '../../contexts/Position'
import {ObjectFitProvider, VALID_FIT} from '../../contexts/ObjectFit'
import {LocaleContext} from '../../contexts/Locale'
import {LanguageProvider} from '../../contexts/Language'

const PlayerContainer = ({
standalone,
Expand All @@ -24,15 +24,15 @@ const PlayerContainer = ({
children,
initialObjectFit = 'contain',
useMSE,
locale = 'en',
language,

Choose a reason for hiding this comment

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

是不是保留 locale 比较好,language 可能有歧义。

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

locale 和 language 还是不一样的。我们看起来只区分了语言,并没有区分「区域」,比如美国和英国词汇不同,数字格式不同之类的。

Copy link
Collaborator

Choose a reason for hiding this comment

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

我们这个属性可以填的值兼容了 locale 的写法,如果本意就是 locale 感觉写 locale 也没什么问题。美国英国的不同 locale 表现成同一种外观而已。

}) => (
<ObjectFitProvider initialObjectFit={initialObjectFit}>
<PositionProvider shouldObserveResize={shouldObserveResize}>
<MessageProvider id={id} enableCrossWindow={standalone}>
<InternalContext.Consumer>
{({emitEvent, subscribeAction}) => (
<VideoSourceProvider onEvent={emitEvent} sources={sources} id={id}>
<LocaleContext.Provider value={locale}>
<LanguageProvider language={language}>
<VideoSourceContext.Consumer>
{({currentSrc}) => (
<Player
Expand All @@ -49,7 +49,7 @@ const PlayerContainer = ({
)}
</VideoSourceContext.Consumer>
{children}
</LocaleContext.Provider>
</LanguageProvider>
</VideoSourceProvider>
)}
</InternalContext.Consumer>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react'
import {LocaleContext} from '../../contexts/Locale'
import {LanguageContext} from '../../contexts/Language'
import strings from './strings'

const TranslatedText = ({name}) => (
<LocaleContext.Consumer>
<LanguageContext.Consumer>
lixiaoyan marked this conversation as resolved.
Show resolved Hide resolved
{locale => strings[locale][name]}
</LocaleContext.Consumer>
</LanguageContext.Consumer>
)

export default TranslatedText
10 changes: 6 additions & 4 deletions packages/griffith/src/components/TranslatedText/strings.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import SUPPORTED_LANGUAGES from '../../contexts/Language/supportedLanguages'

export default {
en: {
[SUPPORTED_LANGUAGES.EN]: {
'quality-auto': 'Auto',
'quality-ld': 'LD',
'quality-sd': 'SD',
'quality-hd': 'HD',
'action-enter-fullscreen': 'Fullscreen',
'action-exit-fullscreen': 'Exit Fullscreen',
},
ja: {
[SUPPORTED_LANGUAGES.JA]: {
'quality-auto': '自動',
'quality-ld': '低画質',
'quality-sd': '標準画質',
Expand All @@ -16,7 +18,7 @@ export default {
'action-exit-fullscreen': '全画面終了',
},
// covers zh-Hans-CN and zh-Hans-SG
'zh-Hans': {
[SUPPORTED_LANGUAGES.ZH_HANS]: {
'quality-auto': '自动',
'quality-ld': '低清',
'quality-sd': '标清',
Expand All @@ -25,7 +27,7 @@ export default {
'action-exit-fullscreen': '退出全屏',
},
// covers zh-Hant-HK and zh-Hant-TW
'zh-Hant': {
[SUPPORTED_LANGUAGES.ZH_HANT]: {
'quality-auto': '自動',
'quality-ld': '低畫質',
'quality-sd': '標準畫質',
Expand Down
6 changes: 6 additions & 0 deletions packages/griffith/src/contexts/Language/LanguageContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react'

const LanguageContext = React.createContext('en')
LanguageContext.displayName = 'LanguageContext'

export default LanguageContext
47 changes: 47 additions & 0 deletions packages/griffith/src/contexts/Language/LanguageProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react'
import PropTypes from 'prop-types'
import LanguageContext from './LanguageContext'
import guessLanguage from './guessLanguage'
import SUPPORTED_LANGUAGES from './supportedLanguages'

const VALID_LANGUAGES = Object.keys(SUPPORTED_LANGUAGES)

const isValid = language => VALID_LANGUAGES.includes(language)

function getLanguage(props) {
const {language} = props
if (isValid(language)) {
return language
} else if (typeof document !== 'undefined') {
// detect language from html lang attributes and navigator.languages
const htmlLanguage = document.documentElement.getAttribute('lang')
const navigatorLanguages = navigator.languages || [navigator.language]
const languageList = [htmlLanguage, ...navigatorLanguages]
return guessLanguage(languageList)
}

return SUPPORTED_LANGUAGES.EN
}

export default class LanguageProvider extends React.PureComponent {
static propTypes = {
language: PropTypes.oneOf(VALID_LANGUAGES),
}

state = {
language: SUPPORTED_LANGUAGES.EN,
}

static getDerivedStateFromProps = (props, state) => {
const language = getLanguage(props)
return language === state.language ? null : {language}
}

render() {
return (
<LanguageContext.Provider value={this.state.language}>
{this.props.children}
</LanguageContext.Provider>
)
}
}
35 changes: 35 additions & 0 deletions packages/griffith/src/contexts/Language/guessLanguage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import SUPPORTED_LANGUAGES from './supportedLanguages'

const KEY_WORDS = [
['en', SUPPORTED_LANGUAGES.EN],
['ja', SUPPORTED_LANGUAGES.JA],

['hant', SUPPORTED_LANGUAGES.ZH_HANT],
['hans', SUPPORTED_LANGUAGES.ZH_HANS],

['hk', SUPPORTED_LANGUAGES.ZH_HANT],
['tw', SUPPORTED_LANGUAGES.ZH_HANT],

['cn', SUPPORTED_LANGUAGES.ZH_HANS],
['sg', SUPPORTED_LANGUAGES.ZH_HANS],

['zh', SUPPORTED_LANGUAGES.ZH_HANS], // prefer Simplified Chinese to Traditional if not specified
]

function matchKeyword(langCode) {
const found = KEY_WORDS.find(([keyword]) => langCode.includes(keyword))
return found ? found[1] : null
}

function guessLanguage(languageList) {
const list = languageList.filter(Boolean).map(s => s.toLocaleLowerCase())
for (let i = 0; i < list.length; i = i + 1) {
const matchedSupportedLanguage = matchKeyword(list[i])
if (matchedSupportedLanguage) {
return matchedSupportedLanguage
}
}
return SUPPORTED_LANGUAGES.EN
}

export default guessLanguage
37 changes: 37 additions & 0 deletions packages/griffith/src/contexts/Language/guessLanguage.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import guessLanguage from './guessLanguage'
import SUPPORTED_LANGUAGES from './supportedLanguages'

describe('guessLanguage', () => {
it('should return first suppored language in the list', () => {
expect(guessLanguage(['zh-tw', 'en'])).toBe(SUPPORTED_LANGUAGES.ZH_HANT)
expect(guessLanguage(['fr', 'en'])).toBe(SUPPORTED_LANGUAGES.EN)
})

it('should return default language if no match found', () => {
expect(guessLanguage(['es', 'de'])).toBe(SUPPORTED_LANGUAGES.EN)
})

it('should return default language if provided empty list', () => {
expect(guessLanguage([])).toBe(SUPPORTED_LANGUAGES.EN)
})

it('handle various Chinese language codes', () => {
expect(guessLanguage(['zh-CN'])).toBe(SUPPORTED_LANGUAGES.ZH_HANS)
expect(guessLanguage(['zh-SG'])).toBe(SUPPORTED_LANGUAGES.ZH_HANS)

expect(guessLanguage(['zh-TW'])).toBe(SUPPORTED_LANGUAGES.ZH_HANT)
expect(guessLanguage(['zh-HK'])).toBe(SUPPORTED_LANGUAGES.ZH_HANT)

expect(guessLanguage(['zh-cmn-Hans'])).toBe(SUPPORTED_LANGUAGES.ZH_HANS)
expect(guessLanguage(['cmn-Hans-CN'])).toBe(SUPPORTED_LANGUAGES.ZH_HANS)
expect(guessLanguage(['zh-cmn-Hans-HK'])).toBe(SUPPORTED_LANGUAGES.ZH_HANS)

expect(guessLanguage(['zh-cmn-Hant'])).toBe(SUPPORTED_LANGUAGES.ZH_HANT)
expect(guessLanguage(['zh-cmn-Hant'])).toBe(SUPPORTED_LANGUAGES.ZH_HANT)
expect(guessLanguage(['zh-cmn-Hant-CN'])).toBe(SUPPORTED_LANGUAGES.ZH_HANT)
})

it('prefer Simplified Chinese to Traditional if not specified', () => {
expect(guessLanguage(['zh'])).toBe(SUPPORTED_LANGUAGES.ZH_HANS)
})
})
2 changes: 2 additions & 0 deletions packages/griffith/src/contexts/Language/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {default as LanguageContext} from './LanguageContext'
export {default as LanguageProvider} from './LanguageProvider'
8 changes: 8 additions & 0 deletions packages/griffith/src/contexts/Language/supportedLanguages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const SUPPORTED_LANGUAGES = {
EN: 'en',
JA: 'hans',
ZH_HANS: 'zh-Hans',
ZH_HANT: 'zh-Hant',
}

export default SUPPORTED_LANGUAGES
6 changes: 0 additions & 6 deletions packages/griffith/src/contexts/Locale/LocaleContext.js

This file was deleted.

1 change: 0 additions & 1 deletion packages/griffith/src/contexts/Locale/index.js

This file was deleted.