Skip to content

Commit

Permalink
Merge pull request #49 from nyavro/react-translation-hook-support
Browse files Browse the repository at this point in the history
React translation hook support
  • Loading branch information
nyavro authored Aug 22, 2020
2 parents 976584e + 5ea6ff6 commit 89ade76
Show file tree
Hide file tree
Showing 47 changed files with 14,094 additions and 61 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@
/examples/vue-i18n-js-config-simple/.idea/**
/examples/vue-i18n-js-config-simple/node_modules/**
/examples/vue-i18n-files-simple/.idea/**
/examples/vue-i18n-files-simple/node_modules/**
/examples/vue-i18n-files-simple/node_modules/**
/examples/react-i18n-using-hooks/node_modules/**
/examples/react-i18n-using-hooks/.idea/**
13,631 changes: 13,631 additions & 0 deletions examples/react-i18n-using-hooks/package-lock.json

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions examples/react-i18n-using-hooks/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "react-i18n-using-hooks",
"version": "0.1.0",
"private": true,
"dependencies": {
"i18next": "19.6.3",
"i18next-browser-languagedetector": "5.0.0",
"i18next-http-backend": "1.0.15",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-i18next": "11.7.0",
"react-scripts": "3.4.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"browserslist": {
"development": [
"last 2 chrome versions",
"last 2 firefox versions",
"last 2 edge versions"
],
"production": [
">1%",
"last 4 versions",
"Firefox ESR",
"not ie < 11"
]
}
}
Binary file not shown.
40 changes: 40 additions & 0 deletions examples/react-i18n-using-hooks/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
5 changes: 5 additions & 0 deletions examples/react-i18n-using-hooks/public/locales/de/hook.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"example1": {
"title": "Hook titel %%%"
}
}
8 changes: 8 additions & 0 deletions examples/react-i18n-using-hooks/public/locales/de/main.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"header": {
"title": "Willkommen zu i18n react mit hooks"
},
"trans": {
"description": "Aus namespace"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"title": "Willkommen zu react und react-i18next",
"description": {
"part1": "Um loszulegen, ändere <1>src/App(DE).js</1> und speichere um neuzuladen.",
"part2": "Wechsle die Sprache zwischen deutsch und englisch mit Hilfe der beiden Schalter."
}
}
5 changes: 5 additions & 0 deletions examples/react-i18n-using-hooks/public/locales/en/hook.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"example": {
"title": "Hook title !!!"
}
}
8 changes: 8 additions & 0 deletions examples/react-i18n-using-hooks/public/locales/en/main.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"header": {
"title": "Welcome to i18n react with hooks"
},
"trans": {
"description": "From namespace"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"title": "Welcome to react using react-i18next",
"description": {
"part1": "To get started, edit <1>src/App.js</1> and save to reload.",
"part2": "Switch language between english and german using buttons above.",
"part3": "Switch language"
}
}
15 changes: 15 additions & 0 deletions examples/react-i18n-using-hooks/public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
24 changes: 24 additions & 0 deletions examples/react-i18n-using-hooks/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.App {
text-align: center;
}

.App-logo {
animation: App-logo-spin infinite 20s linear;
height: 80px;
}

.App-header {
background-color: #222;
height: 150px;
padding: 20px;
color: white;
}

.App-intro {
font-size: large;
}

@keyframes App-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
9 changes: 9 additions & 0 deletions examples/react-i18n-using-hooks/src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import './App.css';
import React from 'react';
import {MainPage} from "./MainPage";


// here app catches the suspense from page in case translations are not yet loaded
export default function App() {
return React.createElement(MainPage);
}
8 changes: 8 additions & 0 deletions examples/react-i18n-using-hooks/src/LegacyWelcomeClass.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React, {Component} from 'react';

export class LegacyWelcomeClass extends Component {
render() {
const {t} = this.props;
return <h2>{t('title')}</h2>;
}
}
69 changes: 69 additions & 0 deletions examples/react-i18n-using-hooks/src/MainPage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, {Suspense} from 'react';
import logo from "./logo.svg";
import {Trans, useTranslation, withTranslation} from "react-i18next";
import {LegacyWelcomeClass} from "./LegacyWelcomeClass";

const Welcome = withTranslation()(LegacyWelcomeClass);

const TransComponent = () => (
<div style={{background: '#ABFFAB'}}>
Trans:
<Trans i18nKey="description.part3">
To get started, edit <code>src/App.js</code> and save to reload. ::SampleComponent::
</Trans>
<br/>
Trans namespace:
<Trans i18nKey="main:trans.description">
(With namespace) To get started, edit <code>src/App.js</code> and save to reload. ::SampleComponent::NS::
</Trans>
</div>
);

const UseTranslationHookComponent = () => {
const { t, i18n } = useTranslation('hook');
return (
<div style={{background: '#32F0AB'}}>
<div>useTranslation hook:</div>
<div>{t('example.title')}</div>
</div>
);
};

const MainComponent = () => {
const { t, i18n } = useTranslation();

const changeLanguage = lng => {
i18n.changeLanguage(lng);
};

return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<Welcome />
<button onClick={() => changeLanguage('de')}>de</button>
<button onClick={() => changeLanguage('en')}>en</button>
</div>
<div className="App-intro">
<TransComponent/>
<UseTranslationHookComponent/>
</div>
<div>{t('description.part2')}</div>
<div>{'Namespace: ' + t('main:header.title')}</div>
</div>
);
}

// loading component for suspense fallback
const Loader = () => (
<div className="App">
<img src={logo} className="App-logo" alt="logo" />
<div>loading...</div>
</div>
);

export const MainPage = () => (
<Suspense fallback={<Loader />}>
<MainComponent/>
</Suspense>
);
27 changes: 27 additions & 0 deletions examples/react-i18n-using-hooks/src/i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import i18n from 'i18next';
import Backend from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';

i18n
// load translation using http -> see /public/locales
// learn more: https://github.com/i18next/i18next-http-backend
.use(Backend)
// detect user language
// learn more: https://github.com/i18next/i18next-browser-languageDetector
.use(LanguageDetector)
// pass the i18n instance to react-i18next.
.use(initReactI18next)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
fallbackLng: 'en',
debug: true,
ns: ['main'],

interpolation: {
escapeValue: false, // not needed for react as it escapes by default
},
});

export default i18n;
5 changes: 5 additions & 0 deletions examples/react-i18n-using-hooks/src/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
9 changes: 9 additions & 0 deletions examples/react-i18n-using-hooks/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

// import i18n (needs to be bundled ;))
import './i18n';

ReactDOM.render(<App />, document.getElementById('root'));
7 changes: 7 additions & 0 deletions examples/react-i18n-using-hooks/src/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,6 @@ interface CallContext {
fun accepts(element: PsiElement): Boolean
}

class CompositeCallContext(private val contexts: List<CallContext>): CallContext {
override fun accepts(element: PsiElement): Boolean = contexts.any {it.accepts(element)}
}

/**
* Reference assistant
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class KeyExtractor {
val search = LocalizationSourceSearch(project)
val settings = Settings.getInstance(project)
val config = settings.config()
val files = search.findFilesByName(i18nKey.ns?.text)
val files = search.findFilesByNames(i18nKey.allNamespaces())
val generators = settings.mainFactory().contentGenerators()
val quickFix = if (files.isEmpty()) {
val contentGenerator = if (config.preferYamlFilesGeneration) YamlContentGenerator() else JsonContentGenerator()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ class KeyRequest {
KeyRequestResult(null, true)
} else {
KeyRequestResult(
parser.parse(listOf(KeyElement.literal(keyStr)),
parser.parse(
Pair(listOf(KeyElement.literal(keyStr)), null),
nsSeparator = config.nsSeparator,
keySeparator = config.keySeparator,
emptyNamespace = config.vue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ abstract class CompositeKeyAnnotatorBase(private val keyExtractor: FullKeyExtrac
}

private fun annotateI18nLiteral(fullKey: FullKey, element: PsiElement, holder: AnnotationHolder) {
val annotationHelper = AnnotationHelper(holder, KeyRangesCalculator(element.textRange, element.text.isQuoted()), element.project)
val files = LocalizationSourceSearch(element.project).findFilesByName(fullKey.ns?.text)
val annotationHelper = AnnotationHelper(
holder,
KeyRangesCalculator(element.textRange.shiftRight(element.text.unQuote().indexOf(fullKey.source)), element.text.isQuoted()),
element.project
)
val files = LocalizationSourceSearch(element.project).findFilesByNames(fullKey.allNamespaces())
if (files.isEmpty()) {
if (fullKey.ns == null) {
annotationHelper.unresolvedDefaultNs(fullKey)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ abstract class CompositeKeyCompletionContributor(private val callContext: CallCo

private fun findCompletions(project: Project, prefix: String, source: String, ns: String?, compositeKey: List<Literal>): List<LookupElementBuilder> {
return groupPlurals(
LocalizationSourceSearch(project).findFilesByName(ns).flatMap {
LocalizationSourceSearch(project).findFilesByNames(ns.nullableToList()).flatMap {
listCompositeKeyVariants(
compositeKey,
PsiElementTree.create(it.element),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package com.eny.i18n.plugin.ide.folding
import com.eny.i18n.plugin.factory.LanguageFactory
import com.eny.i18n.plugin.ide.settings.Config
import com.eny.i18n.plugin.ide.settings.Settings
import com.eny.i18n.plugin.key.FullKey
import com.eny.i18n.plugin.key.parser.KeyParser
import com.eny.i18n.plugin.tree.CompositeKeyResolver
import com.eny.i18n.plugin.tree.PropertyReference
import com.eny.i18n.plugin.tree.PsiElementTree
import com.eny.i18n.plugin.key.FullKey
import com.eny.i18n.plugin.utils.LocalizationSourceSearch
import com.eny.i18n.plugin.utils.ellipsis
import com.eny.i18n.plugin.utils.unQuote
Expand Down Expand Up @@ -58,7 +58,7 @@ abstract class FoldingBuilderBase(private val languageFactory: LanguageFactory)

private fun resolve(element: PsiElement, search: LocalizationSourceSearch, config: Config, fullKey: FullKey): ElementToReferenceBinding? {
return search
.findFilesByName(fullKey.ns?.text)
.findFilesByNames(fullKey.allNamespaces())
.filter {
if (config.vue) it.name.contains(config.foldingPreferredLanguage)
else it.parent == config.foldingPreferredLanguage
Expand Down
Loading

0 comments on commit 89ade76

Please sign in to comment.