Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions examples/by-frameworks/fluent-react/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"i18n-ally.localesPaths": ["src/locales"],
"i18n-ally.enabledFrameworks": ["fluent-react"]
}
36 changes: 36 additions & 0 deletions examples/by-frameworks/fluent-react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "fluent-react-example",
"version": "0.1.0",
"private": true,
"dependencies": {
"@fluent/react": "^0.15.2",
"@fluent/bundle": "^0.17.1",
"@fluent/langneg": "^0.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
30 changes: 30 additions & 0 deletions examples/by-frameworks/fluent-react/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.App {
text-align: center;
}

.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}

button {
font-size: 1rem;
padding: 0.5rem 1rem;
margin: 1rem;
border: none;
border-radius: 4px;
background-color: #61dafb;
color: #282c34;
cursor: pointer;
transition: background-color 0.3s;
}

button:hover {
background-color: #4fa8c7;
}
48 changes: 48 additions & 0 deletions examples/by-frameworks/fluent-react/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { useState, useEffect } from 'react'
import { LocalizationProvider, Localized } from '@fluent/react'
import { createLocalization } from './i18n'
import './App.css'

function App() {
const [l10n, setL10n] = useState(null)
const [currentLocale, setCurrentLocale] = useState('en')

useEffect(() => {
const localization = createLocalization([currentLocale])
setL10n(localization)
}, [currentLocale])

if (!l10n) {
return <div>Loading...</div>
}

return (
<LocalizationProvider l10n={l10n}>
<div className="App">
<header className="App-header">
<Localized id="welcome">
<h1>Welcome</h1>
</Localized>

<Localized id="description">
<p>Description</p>
</Localized>

<div>
<Localized id="current-language" vars={{ lang: currentLocale }}>
<p>Current Language: en</p>
</Localized>

<Localized id="change-language">
<button type="button" onClick={() => setCurrentLocale(currentLocale === 'en' ? 'ja' : 'en')}>
Change Language
</button>
</Localized>
</div>
</header>
</div>
</LocalizationProvider>
)
}

export default App
26 changes: 26 additions & 0 deletions examples/by-frameworks/fluent-react/src/i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ReactLocalization } from '@fluent/react'
import { negotiateLanguages } from '@fluent/langneg'
import { FluentBundle } from '@fluent/bundle'

const locales = ['en', 'ja']
const defaultLocale = 'ja'

export async function* generateBundles(userLocales) {
const currentLocales = negotiateLanguages(
userLocales,
locales,
{ defaultLocale }
)

for (const locale of currentLocales) {
const response = await fetch(`/locales/${locale}.ftl`)
const text = await response.text()
const bundle = new FluentBundle(locale)
bundle.addResource(new FluentResource(text))
yield bundle
}
}

export function createLocalization(userLocales) {
return new ReactLocalization(generateBundles(userLocales))
}
4 changes: 4 additions & 0 deletions examples/by-frameworks/fluent-react/src/locales/en.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
welcome = Welcome to Fluent React Example
description = This is a sample application using Fluent React for internationalization.
change-language = Change Language
current-language = Current Language: { $lang }
4 changes: 4 additions & 0 deletions examples/by-frameworks/fluent-react/src/locales/ja.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
welcome = Fluent React サンプルへようこそ
description = これは Fluent React を使用した国際化のサンプルアプリケーションです。
change-language = 言語を変更
current-language = 現在の言語: { $lang }
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,7 @@
"jekyll",
"fluent-vue",
"fluent-vue-sfc",
"fluent-react",
"next-intl",
"next-international"
]
Expand Down
83 changes: 83 additions & 0 deletions src/frameworks/fluent-react.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import type { TextDocument } from 'vscode'
import { Framework } from './base'
import type { ScopeRange } from './base'
import type { LanguageId } from '~/utils'
import { extractionsParsers, DefaultExtractionRules, DefaultDynamicExtractionsRules } from '~/extraction'

class FluentReactFramework extends Framework {
id = 'fluent-react'
display = '@fluent/react'

detection = {
packageJSON: [
'@fluent/react',
],
}

languageIds: LanguageId[] = [
'javascript',
'typescript',
'javascriptreact',
'typescriptreact',
]

enabledParsers = ['ftl']

usageMatchRegex = [
'<Localized\\s+id=[\'"`]({key})[\'"`]',
'useLocalization\\(\\)[^}]*?getString\\([\'"`]({key})[\'"`]',
]

supportAutoExtraction = [
'javascript',
'typescript',
'javascriptreact',
'typescriptreact',
]

detectHardStrings(doc: TextDocument) {
const text = doc.getText()

return extractionsParsers.babel.detect(
text,
DefaultExtractionRules,
DefaultDynamicExtractionsRules,
)
}

refactorTemplates(keypath: string) {
return [
`<Localized id="${keypath}">`,
`getString('${keypath}')`,
keypath,
]
}

getScopeRange(document: TextDocument): ScopeRange[] | undefined {
if (!this.languageIds.includes(document.languageId as LanguageId))
return

const ranges: ScopeRange[] = []
const text = document.getText()

// Add namespace from LocalizationProvider
const regProvider = /<LocalizationProvider[^>]*?l10n={[^}]*?bundles:\s*\[[^\]]*?['"`]([^'"`]+)['"`]/g

for (const match of text.matchAll(regProvider)) {
if (typeof match.index !== 'number')
continue

if (match[1]) {
ranges.push({
start: match.index,
end: match.index + match[0].length,
namespace: match[1],
})
}
}

return ranges
}
}

export default FluentReactFramework
2 changes: 2 additions & 0 deletions src/frameworks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import GeneralFramework from './general'
import LinguiFramework from './lingui'
import JekyllFramework from './jekyll'
import FluentVueSFCFramework from './fluent-vue-sfc'
import FluentReactFramework from './fluent-react'
import i18n from '~/i18n'
import { Log } from '~/utils'

Expand All @@ -55,6 +56,7 @@ export const frameworks: Framework[] = [
new NextInternationalFramework(),
new I18nTagFramework(),
new FluentVueFramework(),
new FluentReactFramework(),
new PhpJoomlaFramework(),
new LaravelFramework(),
new ChromeExtensionFramework(),
Expand Down