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

Fixes usage report for non-missing derived keys #957

Closed
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"i18n-ally.localesPaths": "public/locales",
"i18n-ally.enabledFrameworks": ["i18next-shopify"],
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json",
"i18n-ally.keystyle": "nested",
"i18n-ally.keysInUse": ["description.part2_whatever"]
}
21 changes: 21 additions & 0 deletions examples/by-frameworks/i18next-shopify/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "react_usinghooks",
"version": "0.1.0",
"private": true,
"dependencies": {
"i18next": "20.3.0",
"i18next-browser-languagedetector": "6.1.1",
"i18next-xhr-backend": "3.2.2",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-i18next": "11.9.0",
"@shopify/i18next-shopify": "0.2.3",
"react-scripts": "4.0.3"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
15 changes: 15 additions & 0 deletions examples/by-frameworks/i18next-shopify/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!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">
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"title": "Zuhause"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"title": "Willkommen zu react und react-i18next",
"description": {
"part1": "Um loszulegen, ändere <1>src/App(DE).js</1> speicheren und neuladen.",
"part2": "Ändere die Sprachen zwischen deutsch und englisch mit Hilfe der beiden Schalter."
},
"count": {
"one": "{{count}} Satz übersetzt!",
"other": "{{count}} Sätze übersetzt!"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"title": "Home"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"title": "hello",
"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."
},
"titlew": "ok",
"foo": {
"bar": "foobar"
},
"count": {
"one": "{{count}} phrase translated!",
"other": "{{count}} phrases translated!"
}
}
10 changes: 10 additions & 0 deletions examples/by-frameworks/i18next-shopify/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.App {
text-align: center;
}

.App-header {
background-color: #222;
height: 100px;
padding: 20px;
color: white;
}
101 changes: 101 additions & 0 deletions examples/by-frameworks/i18next-shopify/src/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-unused-vars */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React, { Component } from "react";
import { useTranslation, withTranslation, Trans } from "react-i18next";
import "./App.css";

// use hoc for class based components
class LegacyWelcomeClass extends Component {
render() {
const { t } = this.props;
return (
<div>
<h2>Plain Text</h2>
<h2>{t("translation.title")}</h2>
</div>
);
}
}
const Welcome = withTranslation()(LegacyWelcomeClass);

// Component using the Trans component
function MyComponent() {
return (
<Trans i18nKey="translation:description.part1">
To get started, edit <code>src/App.js</code> and save to reload.
</Trans>
);
}

// page uses the hook
function Page() {
const { t, i18n } = useTranslation();

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

return (
<div className="App">
<div className="App-header">
<Welcome />
<button onClick={() => changeLanguage("de")}>de</button>
<button onClick={() => changeLanguage("en")}>en</button>
</div>
<div className="App-intro">
<MyComponent />
</div>
<div>{t("translation.description.part2")}</div>
{/* plain <Trans>, #423 */}
<Trans i18nKey="translation.description.part2">Fallback text</Trans>
<p>{t("translation.count", { count: 1 })}</p>
</div>
);
}

// hook with scope
function Page2() {
const { t } = useTranslation(["translation.foo"]);

// inside default namespace ("foo.bar")
t("bar");

// explicit namespace
t("pages.home:title");
t("pages/home:title");
}

// hook with another scope
function Page3() {
const { t } = useTranslation("pages/home");

t("title");

// explicit namespace
t("translation:title");
}

// hook with scope in options
function Page4() {
const { t } = useTranslation("pages/home");

t("title");

// explicit namespace
t("title", { ns: "translation" });
}

// component with scope in props
function Page5() {
const { t } = useTranslation("pages/home");

return (
<div className="App">
<Trans t={t} i18nKey="title"></Trans>
{/* explicit namespace */}
<Trans t={t} i18nKey="title" ns="translation"></Trans>
</div>
);
}
31 changes: 31 additions & 0 deletions examples/by-frameworks/i18next-shopify/src/i18n.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import React from "react";
import i18n from "i18next";
import Backend from "i18next-xhr-backend";
import LanguageDetector from "i18next-browser-languagedetector";
import { initReactI18next } from "react-i18next";
import ShopifyFormat from "@shopify/i18next-shopify";

i18n
// load translation using xhr -> see /public/locales
// learn more: https://github.com/i18next/i18next-xhr-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)
// configure with Shopify-specific formats
.use(ShopifyFormat)
// init i18next
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
fallbackLng: "en",
debug: true,

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

export default i18n;
5 changes: 5 additions & 0 deletions examples/by-frameworks/i18next-shopify/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;
}
10 changes: 10 additions & 0 deletions examples/by-frameworks/i18next-shopify/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
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'))
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{
"i18n-ally.localesPaths": "public/locales",
"i18n-ally.enabledFrameworks": [
"react-i18next",
"general"
"react-i18next"
],
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.json",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,7 @@
"ngx-translate",
"i18next",
"react-i18next",
"i18next-shopify",
"i18n-tag",
"flutter",
"vue-sfc",
Expand Down
24 changes: 21 additions & 3 deletions src/core/Analyst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,18 +127,17 @@ export class Analyst {
const allKeys = CurrentFile.loader.keys.map(i => this.normalizeKey(i))
// keys occur in your code
const inUseKeys = uniq([...usages.map(i => i.keypath), ...Config.keysInUse].map(i => this.normalizeKey(i)))

// keys in use
const activeKeys = inUseKeys.filter(i => allKeys.includes(i))
// keys not in use
let idleKeys = allKeys
.filter(i => !inUseKeys.includes(i))
.filter(i => !micromatch.isMatch(i, Config.keysInUse))
// keys in use, but actually you don't have them
const missingKeys = inUseKeys.filter(i => !allKeys.includes(i))
let missingKeys = inUseKeys.filter(i => !allKeys.includes(i))

// remove dervied keys from idle, if the source key is in use
const rules = Global.derivedKeyRules
// remove derived keys from idle, if the source key is in use
idleKeys = idleKeys.filter((key) => {
for (const r of rules) {
const match = r.exec(key)
Expand All @@ -148,6 +147,25 @@ export class Analyst {
return true
})

// for derived keys whose source key is considered missing
// (is actually in use, could be a nested pluralization key scenario)
// - add the source key to active
// - remove the source key from missing
// - remove the derived key from idle
const missingKeysShouldBeActive: string[] = []
idleKeys = idleKeys.filter((key) => {
for (const r of rules) {
const match = r.exec(key)
if (match && match[1] && missingKeys.includes(match[1])) {
missingKeysShouldBeActive.push(match[1])
return false
}
}
return true
})
activeKeys.push(...uniq(missingKeysShouldBeActive))
missingKeys = missingKeys.filter(i => !missingKeysShouldBeActive.includes(i))

const report = {
active: usages.filter(i => activeKeys.includes(i.keypath)),
missing: usages.filter(i => missingKeys.includes(i.keypath)),
Expand Down
39 changes: 39 additions & 0 deletions src/frameworks/i18next-shopify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import ReactI18nextFramework from './react-i18next'
import { DirStructure, KeyStyle } from '~/core'

class ShopifyI18nextFramework extends ReactI18nextFramework {
id = 'i18next-shopify'
display = 'Shopify I18next'

perferredKeystyle?: KeyStyle = 'nested'
perferredDirStructure?: DirStructure = 'file'

detection = {
packageJSON: [
'@shopify/i18next-shopify',
],
}

derivedKeyRules = [
'{key}.plural',
'{key}.0',
'{key}.1',
'{key}.2',
'{key}.3',
'{key}.4',
'{key}.5',
'{key}.6',
'{key}.7',
'{key}.8',
'{key}.9',
// support v4 format as well as v3
'{key}.zero',
'{key}.one',
'{key}.two',
'{key}.few',
'{key}.many',
'{key}.other',
]
}

export default ShopifyI18nextFramework
2 changes: 2 additions & 0 deletions src/frameworks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import FluentVueFramework from './fluent-vue'
import ReactFramework from './react-intl'
import I18nextFramework from './i18next'
import ReactI18nextFramework from './react-i18next'
import ShopifyI18nextFramework from './i18next-shopify'
import VSCodeFramework from './vscode'
import NgxTranslateFramework from './ngx-translate'
import I18nTagFramework from './i18n-tag'
Expand Down Expand Up @@ -46,6 +47,7 @@ export const frameworks: Framework[] = [
new FlutterFramework(),
new EmberFramework(),
new I18nextFramework(),
new ShopifyI18nextFramework(),
new ReactI18nextFramework(),
new I18nTagFramework(),
new FluentVueFramework(),
Expand Down
Loading