├── CODEOWNERS ├── examples ├── react │ ├── .env │ ├── tsconfig.json │ ├── README.md │ ├── package.json │ ├── public │ │ └── index.html │ └── src │ │ └── index.tsx ├── vue │ ├── .browserslistrc │ ├── babel.config.js │ ├── src │ │ ├── shims-vue.d.ts │ │ ├── assets │ │ │ └── logo.png │ │ ├── main.ts │ │ ├── shims-tsx.d.ts │ │ ├── App.vue │ │ ├── components │ │ │ └── HelloWorld.vue │ │ └── i18n.ts │ ├── public │ │ ├── favicon.ico │ │ └── index.html │ ├── .gitignore │ ├── package.json │ ├── tsconfig.json │ └── README.md ├── express-basic │ ├── locales │ │ ├── de │ │ │ └── translation.json │ │ └── en │ │ │ └── translation.json │ ├── package.json │ ├── README.md │ ├── index.js │ └── yarn.lock ├── nextjs │ ├── public │ │ ├── favicon.ico │ │ ├── static │ │ │ └── locales │ │ │ │ ├── de │ │ │ │ └── common.json │ │ │ │ └── en │ │ │ │ └── common.json │ │ └── vercel.svg │ ├── pages │ │ ├── _app.js │ │ └── index.js │ ├── .gitignore │ ├── package.json │ ├── i18n.js │ └── README.md └── basic │ ├── src │ ├── index.html │ └── index.ts │ ├── README.md │ └── package.json ├── docs ├── .vuepress │ ├── styles │ │ ├── index.styl │ │ └── palette.styl │ ├── public │ │ └── phrase-logo.jpg │ ├── mixins │ │ └── scrollAnchorIntoView.js │ └── config.js ├── index.md ├── api │ └── index.md ├── guide │ └── index.md └── examples │ └── index.md ├── index.ts ├── src ├── typings │ ├── helpers.ts │ └── global.ts ├── phrase-config.type.ts └── phrase.ts ├── .editorconfig ├── .github ├── dependabot.yml ├── workflows │ ├── lint.yml │ ├── test-coverage.yml │ ├── release.yml │ ├── deploy-docs.yml │ └── close_inactive_issues.yml └── ISSUE_TEMPLATE │ └── bug_report.md ├── .npmignore ├── .gitignore ├── release.config.js ├── tsconfig.json ├── jest.config.js ├── .eslintrc.js ├── LICENSE ├── package.json ├── README.md ├── CHANGELOG.md └── tests └── phrase.spec.test.ts /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @phrase/strings-create-manage 2 | -------------------------------------------------------------------------------- /examples/react/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /examples/vue/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/index.styl: -------------------------------------------------------------------------------- 1 | .fw-normal 2 | font-weight normal 3 | 4 | .badge 5 | font-weight 600 6 | -------------------------------------------------------------------------------- /examples/vue/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /examples/express-basic/locales/de/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Ein einfaches Beispiel", 3 | "intro": "Intro" 4 | } -------------------------------------------------------------------------------- /examples/express-basic/locales/en/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "A simple example", 3 | "intro": "An intro" 4 | } 5 | -------------------------------------------------------------------------------- /examples/vue/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue;' { 2 | import Vue from 'vue'; 3 | export default Vue; 4 | } 5 | -------------------------------------------------------------------------------- /examples/vue/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phrase/i18next-phrase-in-context-editor-post-processor/HEAD/examples/vue/public/favicon.ico -------------------------------------------------------------------------------- /examples/nextjs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phrase/i18next-phrase-in-context-editor-post-processor/HEAD/examples/nextjs/public/favicon.ico -------------------------------------------------------------------------------- /examples/vue/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phrase/i18next-phrase-in-context-editor-post-processor/HEAD/examples/vue/src/assets/logo.png -------------------------------------------------------------------------------- /docs/.vuepress/public/phrase-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phrase/i18next-phrase-in-context-editor-post-processor/HEAD/docs/.vuepress/public/phrase-logo.jpg -------------------------------------------------------------------------------- /examples/nextjs/public/static/locales/de/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Ein einfaches Beispiel", 3 | "intro": "Intro", 4 | "change-locale": "Wechseln Locale" 5 | } -------------------------------------------------------------------------------- /examples/nextjs/public/static/locales/en/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "A simple example", 3 | "intro": "An intro", 4 | "change-locale": "Change locale" 5 | } 6 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import PhraseInContextEditorPostProcessor from './src/phrase'; 2 | 3 | export * from './src/phrase-config.type'; 4 | export default PhraseInContextEditorPostProcessor; 5 | -------------------------------------------------------------------------------- /src/typings/helpers.ts: -------------------------------------------------------------------------------- 1 | export type DeepPartial = T extends Function ? T : ( 2 | T extends object 3 | ? { [P in keyof T]?: DeepPartial; } 4 | : T 5 | ); 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /examples/vue/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from '@/App.vue'; 3 | import { initializeI18next } from '@/i18n'; 4 | 5 | const app = createApp(App); 6 | 7 | await initializeI18next(); 8 | 9 | app.mount('#app'); 10 | -------------------------------------------------------------------------------- /src/typings/global.ts: -------------------------------------------------------------------------------- 1 | import { PhraseConfig } from '../phrase-config.type'; 2 | 3 | declare global { 4 | // eslint-disable-next-line vars-on-top, no-var 5 | var PHRASEAPP_ENABLED: boolean; 6 | // eslint-disable-next-line vars-on-top, no-var 7 | var PHRASEAPP_CONFIG: PhraseConfig; 8 | } 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: 'npm' 5 | # this will ensure that dependabot will only scan the packages in the root directory 6 | # omitting the packages in the /examples directory 7 | directory: '/' 8 | schedule: 9 | interval: 'monthly' 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ignore everything 2 | ** 3 | * 4 | 5 | # unignore files needed in the package 6 | !/tsconfig.json 7 | !/docs/**.md 8 | # unignore dist files explicitly to remove unwanted demo.file generated by vue-cli https://github.com/vuejs/vue-cli/issues/3291 9 | !/dist/*.?(js|ts|map) 10 | !/dist/src/** 11 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: [push, pull_request] 3 | jobs: 4 | lint: 5 | name: Lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | - uses: actions/setup-node@v3 10 | with: 11 | node-version: '18.x' 12 | - run: yarn install 13 | - run: yarn lint 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | coverage 4 | dist 5 | release 6 | .cache 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /examples/vue/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yml: -------------------------------------------------------------------------------- 1 | name: Test coverage 2 | on: [push, pull_request] 3 | jobs: 4 | lint: 5 | name: Lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v3 9 | - uses: actions/setup-node@v3 10 | with: 11 | node-version: '18.x' 12 | - run: yarn install 13 | - run: yarn test.coverage 14 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "plugins": [ 3 | "@semantic-release/commit-analyzer", 4 | "@semantic-release/release-notes-generator", 5 | "@semantic-release/changelog", 6 | ["@semantic-release/npm", { 7 | "tarballDir": "release" 8 | }], 9 | "@semantic-release/git" 10 | ], 11 | "preset": "angular" 12 | }; 13 | -------------------------------------------------------------------------------- /examples/vue/src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue'; 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | type Element = VNode; 7 | // tslint:disable no-empty-interface 8 | type ElementClass = Vue; 9 | interface IntrinsicElements { 10 | [elem: string]: any; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/nextjs/pages/_app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from 'next/app'; 3 | import { appWithTranslation } from '../i18n'; 4 | 5 | class MyApp extends App { 6 | render() { 7 | const { Component, pageProps } = this.props; 8 | return ( 9 | 10 | ); 11 | } 12 | } 13 | 14 | export default appWithTranslation(MyApp); 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Expected behavior** 11 | 12 | **Actual behavior** 13 | 14 | **Your environment** 15 | version, browser, and operating system 16 | 17 | **Steps to reproduce the problem** 18 | Ideally, provide a minimally reproducible example 19 | -------------------------------------------------------------------------------- /docs/.vuepress/mixins/scrollAnchorIntoView.js: -------------------------------------------------------------------------------- 1 | const scrollToHash = ($route, timeout) => { 2 | setTimeout(() => { 3 | if ($route.hash) { 4 | const element = document.getElementById($route.hash.slice(1)); 5 | 6 | if (element && element.scrollIntoView) { 7 | element.scrollIntoView(); 8 | } 9 | } 10 | }, timeout); 11 | }; 12 | 13 | module.exports = { 14 | mounted () { 15 | scrollToHash(this.$route, 800); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /examples/nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: NPM release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | release: 8 | name: Release 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | - name: Install dependencies 14 | run: yarn 15 | - name: Build package 16 | run: yarn build 17 | - name: Semantic Release 18 | uses: cycjimmy/semantic-release-action@v3 19 | env: 20 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 21 | -------------------------------------------------------------------------------- /docs/.vuepress/styles/palette.styl: -------------------------------------------------------------------------------- 1 | $accentColor = rgb(38, 166, 154) 2 | $textColor = #242A31 3 | $borderColor = rgb(230, 236, 241) 4 | $codeBgColor = #282c34 5 | $arrowBgColor = #ccc 6 | $badgeTipColor = rgb(38, 166, 154) 7 | $badgeWarningColor = darken(#ffe564, 35%) 8 | $badgeErrorColor = #DA5961 9 | 10 | // layout 11 | $navbarHeight = 3.6rem 12 | $sidebarWidth = 20rem 13 | $contentWidth = 740px 14 | $homePageWidth = 960px 15 | 16 | // responsive breakpoints 17 | $MQNarrow = 1151px 18 | $MQMobile = 919px 19 | $MQMobileNarrow = 419px 20 | -------------------------------------------------------------------------------- /examples/vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | 22 | -------------------------------------------------------------------------------- /examples/express-basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18next-express-basic", 3 | "version": "1.0.0", 4 | "description": "Node Express server with i18next.", 5 | "main": "index.js", 6 | "type": "commonjs", 7 | "license": "MIT", 8 | "scripts": { 9 | "start": "node index.js" 10 | }, 11 | "dependencies": { 12 | "express": "4.17.1", 13 | "i18next": "19.4.4", 14 | "i18next-fs-backend": "1.0.2", 15 | "i18next-http-middleware": "1.0.1", 16 | "i18next-phrase-in-context-editor-post-processor": "link:../.." 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18next-phrase-in-context-editor-post-processor-example-basic", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "dev": "next", 7 | "start": "next start -p ${PORT:=3000}", 8 | "build": "next build" 9 | }, 10 | "dependencies": { 11 | "i18next": "^19.6.0", 12 | "i18next-phrase-in-context-editor-post-processor": "link:../..", 13 | "next": "^10.0.6", 14 | "next-i18next": "^7.0.1", 15 | "react": "17.0.1", 16 | "react-dom": "17.0.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "strict": true, 6 | "declaration": true, 7 | "declarationDir": "dist", 8 | "declarationMap": true, 9 | "jsx": "preserve", 10 | "importHelpers": true, 11 | "moduleResolution": "node", 12 | "experimentalDecorators": true, 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "sourceMap": true, 16 | "baseUrl": "." 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue", 3 | "version": "0.1.0", 4 | "private": true, 5 | "license": "MIT", 6 | "scripts": { 7 | "serve": "vue-cli-service serve", 8 | "build": "vue-cli-service build" 9 | }, 10 | "dependencies": { 11 | "i18next": "^22.0.0", 12 | "i18next-phrase-in-context-editor-post-processor": "link:../..", 13 | "vue": "^3.0.0" 14 | }, 15 | "devDependencies": { 16 | "@vue/cli-plugin-typescript": "^5.0.8", 17 | "@vue/cli-service": "^5.0.8", 18 | "typescript": "^5.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/vue/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 17 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'jsdom', 3 | testPathIgnorePatterns: ['/node_modules/', '/examples/'], 4 | transform: { 5 | '^.+\\.tsx?$': 'ts-jest' 6 | }, 7 | moduleFileExtensions: [ 8 | 'ts', 9 | 'js' 10 | ], 11 | coveragePathIgnorePatterns: [ 12 | '/node_modules/', 13 | '/.*\\.type\\.ts' 14 | ], 15 | coverageThreshold: { 16 | global: { 17 | branches: 100, 18 | functions: 100, 19 | lines: 100, 20 | statements: 100, 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /examples/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "lib": [ 7 | "dom", 8 | "dom.iterable", 9 | "esnext" 10 | ], 11 | "allowJs": true, 12 | "skipLibCheck": true, 13 | "esModuleInterop": true, 14 | "noImplicitAny": false, 15 | "allowSyntheticDefaultImports": true, 16 | "strict": true, 17 | "forceConsistentCasingInFileNames": true, 18 | "moduleResolution": "nodenext", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "noEmit": true 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/deploy-docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy docs 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | build-and-deploy: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 🛎️ 11 | uses: actions/checkout@v3 12 | with: 13 | persist-credentials: false 14 | - name: Install and Build 🔧 15 | run: | 16 | yarn install 17 | yarn docs.build 18 | - name: Deploy 🚀 19 | uses: JamesIves/github-pages-deploy-action@v4 20 | with: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | BRANCH: gh-pages 23 | FOLDER: docs/.vuepress/dist 24 | -------------------------------------------------------------------------------- /examples/vue/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "noImplicitAny": false, 9 | "allowJs": true, 10 | "moduleResolution": "nodenext", 11 | "esModuleInterop": true, 12 | "sourceMap": true, 13 | "baseUrl": ".", 14 | "paths": { 15 | "@/*": [ 16 | "src/*" 17 | ] 18 | }, 19 | "lib": [ 20 | "esnext", 21 | "dom" 22 | ] 23 | }, 24 | "include": [ 25 | "src/**/*.ts", 26 | "src/**/*.tsx", 27 | "src/**/*.vue", 28 | "tests/**/*.ts", 29 | "tests/**/*.tsx" 30 | ], 31 | "exclude": [ 32 | "node_modules" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /.github/workflows/close_inactive_issues.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 1 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | steps: 12 | - uses: actions/stale@v5 13 | with: 14 | days-before-issue-stale: 30 15 | days-before-issue-close: 7 16 | stale-issue-label: "stale" 17 | stale-issue-message: "Hey! This issue is still open, but there hasn't been any activity for a month now, so we will be marking this issue as stale and closing it in a week if it's still inactive." 18 | close-issue-message: "This issue was closed because it has been inactive for one week since being marked as stale." 19 | -------------------------------------------------------------------------------- /examples/vue/src/i18n.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import i18next from "i18next"; 4 | import PhraseInContextEditorPostProcessor from "i18next-phrase-in-context-editor-post-processor"; 5 | import { ref } from "vue"; 6 | 7 | export const i18nextInstance = i18next.createInstance({ 8 | postProcess: ['phraseInContextEditor'] 9 | }); 10 | 11 | export const initializeI18next = async () => { 12 | i18nextInstance 13 | .use(new PhraseInContextEditorPostProcessor({ 14 | phraseEnabled: true, 15 | projectId: '00000000000000004158e0858d2fa45c', 16 | accountId: '0bed59e5', 17 | useOldICE: false, 18 | })); 19 | await i18nextInstance.init(); 20 | }; 21 | export const useTranslate = () => { 22 | return {t:ref(i18nextInstance.t.bind(i18nextInstance))}; 23 | }; 24 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | extends: [ 7 | 'eslint-config-phrase' 8 | ], 9 | parserOptions: { 10 | ecmaVersion: 2020, 11 | project: './tsconfig.json' 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 16 | 'class-methods-use-this': 'off', 17 | }, 18 | overrides: [ 19 | { 20 | files: [ 21 | '**/__tests__/*.{j,t}s?(x)', 22 | '**/tests/unit/**/*.spec.{j,t}s?(x)', 23 | ], 24 | env: { 25 | jest: true, 26 | }, 27 | }, 28 | ], 29 | }; 30 | -------------------------------------------------------------------------------- /examples/nextjs/i18n.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const PhraseInContextEditorPostProcessor = process.browser 3 | ? require('i18next-phrase-in-context-editor-post-processor').default 4 | : require('i18next-phrase-in-context-editor-post-processor'); 5 | const NextI18Next = require('next-i18next').default; 6 | 7 | module.exports = new NextI18Next({ 8 | defaultLanguage: 'en', 9 | otherLanguages: ['de'], 10 | localePath: path.resolve('./public/static/locales'), 11 | use: [ 12 | new PhraseInContextEditorPostProcessor({ 13 | phraseEnabled: true, 14 | projectId: '00000000000000004158e0858d2fa45c', 15 | accountId: '0bed59e5', 16 | useOldICE: false, 17 | }) 18 | ], 19 | postProcess: ['phraseInContextEditor'] 20 | }); 21 | -------------------------------------------------------------------------------- /examples/basic/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | I18next example 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 17 | 20 |
21 |
22 | 23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | # i18next Phrase In-Context Editor post processor basic demo 2 | 3 | ## How to use the demo 4 | 5 | ### Install dependencies & run 6 | 7 | ```bash 8 | yarn 9 | yarn start 10 | ``` 11 | 12 | Look into your console for the development server address (by default it's [http://localhost:1234](http://localhost:1234)) and open it in the browser. 13 | 14 | ### Login to Phrase demo project 15 | 16 | This demo uses special, demo [Phrase](https://phrase.com) project. To login, use authentication data provided below: 17 | 18 | ```bash 19 | E-Mail: demo@phrase.com 20 | Password: phrase 21 | ``` 22 | 23 | ## More information 24 | 25 | For more info, please have a look at [i18next Phrase In-Context Editor post processor documentation](https://phrase.github.io/i18next-phrase-in-context-editor-post-processor/). 26 | -------------------------------------------------------------------------------- /examples/vue/README.md: -------------------------------------------------------------------------------- 1 | # i18next Phrase In-Context Editor post processor vue demo 2 | 3 | ## How to use the demo 4 | 5 | ### Install dependencies and run 6 | 7 | ```bash 8 | yarn 9 | yarn serve 10 | ``` 11 | 12 | Look into your console for the development server address (by default, it's [http://localhost:8080](http://localhost:8080). 13 | 14 | The page will reload if you make edits. 15 | Any lint errors will be visible in the console. 16 | 17 | ### Login to Phrase demo project 18 | 19 | This demo uses a special, demo [Phrase](https://phrase.com) project. To login, use the authentication data provided below: 20 | 21 | ```bash 22 | E-Mail: demo@phrase.com 23 | Password: phrase 24 | ``` 25 | 26 | ## More information 27 | 28 | For more info, please have a look at [i18next Phrase In-Context Editor post processor readme](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor). 29 | -------------------------------------------------------------------------------- /examples/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18next-phrase-in-context-editor-post-processor-example-basic", 3 | "version": "0.0.1", 4 | "main": "index.js", 5 | "author": "Jakub Freisler ", 6 | "license": "MIT", 7 | "scripts": { 8 | "clean:dist": "rimraf 'dist/*.?(css|js|map|html)'", 9 | "prebuild": "yarn clean:dist", 10 | "build": "parcel build src/index.html --experimental-scope-hoisting", 11 | "prestart": "yarn clean:dist", 12 | "start": "parcel src/index.html" 13 | }, 14 | "devDependencies": { 15 | "parcel-bundler": "^1.12.4", 16 | "rimraf": "^3.0.2", 17 | "typescript": "^3.9.5" 18 | }, 19 | "dependencies": { 20 | "i18next": "^19.5.1", 21 | "i18next-browser-languagedetector": "^5.0.0", 22 | "i18next-phrase-in-context-editor-post-processor": "link:../..", 23 | "i18next-xhr-backend": "^3.2.2" 24 | }, 25 | "resolutions": { 26 | "**/node-forge": "^0.10.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/react/README.md: -------------------------------------------------------------------------------- 1 | # i18next Phrase In-Context Editor post processor react demo 2 | 3 | ## How to use the demo 4 | 5 | ### Install dependencies and run 6 | 7 | ```bash 8 | yarn 9 | yarn start 10 | ``` 11 | 12 | New tab in your default browser should open. If that's not the case look into your console for the development server address (by default, it's [http://localhost:3000](http://localhost:3000). 13 | 14 | The page will reload if you make edits. 15 | Any lint errors will be visible in the console. 16 | 17 | ### Login to Phrase demo project 18 | 19 | This demo uses a special, demo [Phrase](https://phrase.com) project. To login, use the authentication data provided below: 20 | 21 | ```bash 22 | E-Mail: demo@phrase.com 23 | Password: phrase 24 | ``` 25 | 26 | ## More information 27 | 28 | For more info, please have a look at [i18next Phrase In-Context Editor post processor readme](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor). 29 | -------------------------------------------------------------------------------- /examples/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18next-phrase-in-context-editor-post-processor-example-react", 3 | "version": "0.0.1", 4 | "main": "index.js", 5 | "author": "Jakub Freisler ", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "react-scripts start", 9 | "build": "react-scripts build" 10 | }, 11 | "devDependencies": { 12 | "typescript": "^5.0.0" 13 | }, 14 | "dependencies": { 15 | "i18next": "^22.5.0", 16 | "i18next-phrase-in-context-editor-post-processor": "link:../..", 17 | "react": "^18.2.0", 18 | "react-dom": "^18.2.0", 19 | "react-i18next": "^12.3.1", 20 | "react-scripts": "^5.0.1" 21 | }, 22 | "browserslist": { 23 | "production": [ 24 | ">0.2%", 25 | "not dead", 26 | "not op_mini all" 27 | ], 28 | "development": [ 29 | "last 1 chrome version", 30 | "last 1 firefox version", 31 | "last 1 safari version" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | Phrase In-Context Editor i18n example react App 12 | 13 | 14 | 15 |
16 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/nextjs/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/phrase-config.type.ts: -------------------------------------------------------------------------------- 1 | export type PhraseConfig = { 2 | phraseEnabled: boolean; 3 | scriptAutoLoad: boolean; 4 | baseUrl: string; 5 | profileUrl: string; 6 | apiBaseUrl: string; 7 | oauthEndpointUrl: string; 8 | helpUrl: string; 9 | logoUrl: string; 10 | stylesheetUrl: string; 11 | version: string; 12 | priorityLocales: string[]; 13 | projectId: string; 14 | accountId: string; 15 | useOldICE: boolean; 16 | branch: string; 17 | ajaxObserver: boolean; 18 | debugMode: boolean; 19 | prefix: string; 20 | suffix: string; 21 | autoLowercase: boolean; 22 | forceLocale: boolean; 23 | loginDialogMessage: string; 24 | telemetryEnabled: boolean; 25 | datacenter: 'eu' | 'us'; 26 | autoLogin: { 27 | perform: boolean; 28 | email: string; 29 | password: string; 30 | }; 31 | sso: { 32 | enabled: boolean; 33 | enforced: boolean; 34 | provider: string; 35 | identifier: string; 36 | }; 37 | fullReparse: boolean; 38 | origin: string; 39 | hidingClasses?: string[]; 40 | }; 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Phrase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/express-basic/README.md: -------------------------------------------------------------------------------- 1 | # i18next Phrase In-Context Editor post processor I18next http middleware (Express.js) demo 2 | 3 | ## How to use the demo 4 | 5 | ### Install dependencies & run 6 | 7 | ```bash 8 | yarn 9 | yarn start 10 | ``` 11 | 12 | In you web browser, open the development server address (by default, it's [http://localhost:8080](http://localhost:8080). 13 | 14 | You need to rerun `yarn start` command every time you've made any change to the server's code. 15 | Any Express errors will be visible within console. 16 | 17 | ### Login to Phrase demo project 18 | 19 | This demo uses special, demo [Phrase](https://phrase.com) project. To login, use authentication data provided below: 20 | 21 | ```bash 22 | E-Mail: demo@phrase.com 23 | Password: phrase 24 | ``` 25 | 26 | ## More information 27 | 28 | For more info, please have a look at [i18next Phrase In-Context Editor post processor readme](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor). 29 | 30 | ### Notice 31 | 32 | This demo was built on top of the basic example from [i18next-http-middleware repository](https://github.com/i18next/i18next-http-middleware/tree/master/example/basic). 33 | -------------------------------------------------------------------------------- /examples/nextjs/README.md: -------------------------------------------------------------------------------- 1 | # i18next Phrase In-Context Editor post processor nextjs demo 2 | 3 | ## How to use the demo 4 | 5 | ### Install dependencies & run 6 | 7 | ```bash 8 | yarn 9 | yarn dev 10 | ``` 11 | 12 | New tab in your default browser should open. If that's not the case look into your console for the development server address (by default, it's [http://localhost:3000](http://localhost:3000). 13 | 14 | The page will reload if you make edits. 15 | Any nextjs errors will be visible within console. 16 | 17 | ### Login to Phrase demo project 18 | 19 | This demo uses special, demo [Phrase](https://phrase.com) project. To login, use authentication data provided below: 20 | 21 | ```bash 22 | E-Mail: demo@phrase.com 23 | Password: phrase 24 | ``` 25 | 26 | ## More information 27 | 28 | For more info, please have a look at [i18next Phrase In-Context Editor post processor readme](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor). 29 | 30 | ### Notice 31 | 32 | This demo was built on top of the starter template from [Learn Next.js guide](https://nextjs.org/learn) and the [next-i18next simple example project](https://github.com/isaachinman/next-i18next/tree/master/examples/simple). 33 | -------------------------------------------------------------------------------- /examples/react/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import i18n from "i18next"; 4 | import { useTranslation, initReactI18next } from "react-i18next"; 5 | import PhraseInContextEditorPostProcessor from "i18next-phrase-in-context-editor-post-processor"; 6 | 7 | await i18n 8 | .use(initReactI18next) 9 | .use(new PhraseInContextEditorPostProcessor({ // Init Phrase ICE 10 | phraseEnabled: true, 11 | projectId: '00000000000000004158e0858d2fa45c', 12 | accountId: '0bed59e5', 13 | useOldICE: false, 14 | prefix: '{{__phrase_', 15 | suffix: '__}}', 16 | })) 17 | .init({ 18 | resources: { 19 | en: { 20 | translation: { 21 | "some_test_key_name": "Welcome to React and react-i18next" 22 | } 23 | } 24 | }, 25 | lng: "en", 26 | fallbackLng: "en", 27 | postProcess: ['phraseInContextEditor'] 28 | }); 29 | 30 | 31 | function App() { 32 | const { t } = useTranslation(); 33 | return

{t('some_test_key_name')}

; 34 | } 35 | 36 | // append app to dom 37 | const root = createRoot(document.getElementById('root')); 38 | root.render( 39 | 40 | ); 41 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | **i18next Phrase In-Context Editor Post Processor** is a package which allows to integrate the [Phrase Strings In-Context Editor](https://phrase.com/blog/posts/use-phrase-in-context-editor) with any Javascript application. The one and only prerequisite is to use the brilliant [i18next](https://www.i18next.com/) library to handle the localization of the app. 4 | 5 | ## Why 6 | 7 | The [Phrase Strings In-Context Editor](https://support.phrase.com/hc/en-us/articles/5784095916188-In-Context-Editor-Strings) adds the functionality of live translation editing while browsing through the production-ready application. It allows translators to edit the content directly within your application without the need to use a separated management platform UI - from now on, translators will always see exactly where content comes from. 8 | 9 | ## How does it work 10 | 11 | This package is a [post processor](https://www.i18next.com/misc/creating-own-plugins#post-processor) for [i18next](https://www.i18next.com/). The main job of **i18next Phrase In-Context Editor Post Processor** is to convert every translation key into a format that is understandable by the [**Phrase Strings In-Context Editor**](https://help.phrase.com/help/configure-in-context-editor). This allows us to gather an synchronize texts between [Phrase.com](https://phrase.com/) and your app. 12 | 13 | So, to get the [Phrase Strings In-Context Editor](https://support.phrase.com/hc/en-us/articles/5784095916188-In-Context-Editor-Strings) integrated with your application, you just need to install and properly include it. Let's have a look how to do that in the next section! 14 | -------------------------------------------------------------------------------- /examples/express-basic/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const i18next = require('i18next'); 3 | const i18nextMiddleware = require('i18next-http-middleware'); 4 | const Backend = require('i18next-fs-backend'); 5 | const PhraseInContextEditorPostProcessor = require('i18next-phrase-in-context-editor-post-processor'); 6 | 7 | const app = express(); 8 | const port = process.env.PORT || 8080; 9 | 10 | const PhraseICEPostProcessorInstance = new PhraseInContextEditorPostProcessor({ 11 | phraseEnabled: true, 12 | scriptAutoLoad: false, 13 | projectId: '00000000000000004158e0858d2fa45c', 14 | accountId: '0bed59e5', 15 | useOldICE: false, 16 | }); 17 | 18 | i18next 19 | .use(Backend) 20 | .use(i18nextMiddleware.LanguageDetector) 21 | .use(PhraseICEPostProcessorInstance) 22 | .init({ 23 | backend: { 24 | loadPath: __dirname + '/locales/{{lng}}/{{ns}}.json', 25 | addPath: __dirname + '/locales/{{lng}}/{{ns}}.missing.json' 26 | }, 27 | debug: true, 28 | fallbackLng: 'en', 29 | preload: ['en', 'de'], 30 | postProcess: ['phraseInContextEditor'] 31 | }); 32 | 33 | const phraseICEScriptHTML = PhraseICEPostProcessorInstance.toScriptHTML(); 34 | 35 | app.use(i18nextMiddleware.handle(i18next)); 36 | 37 | app.get('/', (req, res) => { 38 | res.send(` 39 |

${req.t('title')}

40 |

${req.t('intro')}

41 | ${phraseICEScriptHTML} 42 | `); 43 | }); 44 | 45 | app.get('/title', (req, res) => { 46 | res.send( 47 | req.t('title') + 48 | phraseICEScriptHTML 49 | ); 50 | }); 51 | 52 | app.listen(port, () => { 53 | console.log(`Server is listening on port ${port}`); 54 | }); 55 | -------------------------------------------------------------------------------- /examples/basic/src/index.ts: -------------------------------------------------------------------------------- 1 | import i18next from 'i18next'; 2 | import i18nextXHRBackend from 'i18next-xhr-backend'; 3 | import i18nextBrowserLanguageDetector from 'i18next-browser-languagedetector'; 4 | import PhraseInContextEditorPostProcessor from 'i18next-phrase-in-context-editor-post-processor'; 5 | 6 | // based on official i18next example https://jsfiddle.net/jamuhl/ferfywyf 7 | 8 | document.querySelectorAll('[data-language]') 9 | .forEach((btn) => 10 | btn.addEventListener('click', () => 11 | i18next.changeLanguage(btn.getAttribute('data-language') ?? 'en') 12 | ) 13 | ); 14 | 15 | const updateContent = () => { 16 | const title = document.getElementById('title'); 17 | const saveBtn = document.getElementById('saveBtn'); 18 | const info = document.getElementById('info'); 19 | 20 | if (title) 21 | title.textContent = i18next.t('title', { what: 'i18next' }); 22 | if (saveBtn) 23 | saveBtn.textContent = i18next.t('common:button.save', { count: Math.floor(Math.random()*2+1) }); 24 | if (info) 25 | info.textContent = `detected user language: "${i18next.language}" --> loaded languages: "${i18next.languages.join(', ')}"`; 26 | }; 27 | 28 | // eslint-disable-next-line @typescript-eslint/no-floating-promises 29 | i18next 30 | .use(i18nextXHRBackend) 31 | .use(i18nextBrowserLanguageDetector) 32 | .use(new PhraseInContextEditorPostProcessor({ 33 | phraseEnabled: true, 34 | projectId: '00000000000000004158e0858d2fa45c', 35 | accountId: '0bed59e5', 36 | useOldICE: false, 37 | })) 38 | .init({ 39 | fallbackLng: 'en', 40 | debug: true, 41 | ns: ['special', 'common'], 42 | defaultNS: 'special', 43 | backend: { 44 | // load from i18next-gitbook repo 45 | loadPath: 'https://raw.githubusercontent.com/i18next/i18next-gitbook/master/locales/{{lng}}/{{ns}}.json', 46 | crossDomain: true 47 | }, 48 | postProcess: ['phraseInContextEditor'] 49 | }, function(err, t) { 50 | updateContent(); 51 | i18next.on('languageChanged', updateContent); 52 | }); 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18next-phrase-in-context-editor-post-processor", 3 | "description": "Plugin for i18next that pairs well with Phrase In-Context Editor", 4 | "version": "1.7.2", 5 | "source": "index.ts", 6 | "main": "dist/i18next-phrase-in-context-editor-post-processor.js", 7 | "unpkg": "dist/i18next-phrase-in-context-editor-post-processor.umd.js", 8 | "module": "dist/i18next-phrase-in-context-editor-post-processor.module.js", 9 | "esmodule": "dist/i18next-phrase-in-context-editor-post-processor.modern.js", 10 | "types": "dist/index.d.ts", 11 | "repository": "https://github.com/phrase/i18next-phrase-in-context-editor-post-processor.git", 12 | "author": "Jakub Freisler ", 13 | "license": "MIT", 14 | "sideEffects": false, 15 | "scripts": { 16 | "build": "microbundle build", 17 | "start": "microbundle watch", 18 | "test": "jest test", 19 | "test.coverage": "yarn test --coverage", 20 | "lint": "eslint '**/*.ts' --ignore-pattern '**/*.d.ts'", 21 | "lint.fix": "yarn lint --fix", 22 | "docs.dev": "export NODE_OPTIONS=--openssl-legacy-provider && vuepress dev docs", 23 | "docs.build": "export NODE_OPTIONS=--openssl-legacy-provider && vuepress build docs", 24 | "release": "semantic-release" 25 | }, 26 | "peerDependencies": { 27 | "i18next": "^19.5.1 || ^20.6.1 || ^21.6.0 || ^22.0.0 || ^23.0.0" 28 | }, 29 | "devDependencies": { 30 | "@semantic-release/changelog": "^5.0.1", 31 | "@semantic-release/git": "^9.0.0", 32 | "@types/jest": "^26.0.3", 33 | "@typescript-eslint/eslint-plugin": "^2.0.0", 34 | "@typescript-eslint/parser": "^2.0.0", 35 | "eslint": "^7.3.1", 36 | "eslint-config-phrase": "https://github.com/phrase/eslint-config-phrase.git", 37 | "jest": "^29.7.0", 38 | "jest-environment-jsdom": "^29.7.0", 39 | "microbundle": "^0.15.1", 40 | "semantic-release": "^19.0.3", 41 | "ts-jest": "^29.1.1", 42 | "typescript": "^4.3.0", 43 | "vuepress": "^1.9.10", 44 | "vuepress-plugin-clean-urls": "^1.1.1", 45 | "vuepress-plugin-seo": "^0.1.3" 46 | }, 47 | "dependencies": { 48 | "@sagi.io/globalthis": "^0.0.2" 49 | }, 50 | "keywords": [ 51 | "i18next", 52 | "i18next post processor", 53 | "Phrase", 54 | "In-Context Editor", 55 | "translate", 56 | "translation", 57 | "i18n", 58 | "l10n", 59 | "localization", 60 | "internationalization" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'i18next Phrase In-Context Editor Post Processor', 3 | description: 'Plugin for i18next that paires well with Phrase In-Context Editor.', 4 | base: '/i18next-phrase-in-context-editor-post-processor/', 5 | themeConfig: { 6 | nav: [ 7 | { text: 'Contact Us', link: 'https://phrase.com/contact' } 8 | ], 9 | sidebar: { 10 | '/guide/': [ 11 | '../', 12 | '', 13 | '../api/', 14 | '../examples/' 15 | ], 16 | '/api/': [ 17 | '../', 18 | '../guide/', 19 | '', 20 | '../examples/' 21 | ], 22 | '/examples/': [ 23 | '../', 24 | '../guide/', 25 | '../api/', 26 | '', 27 | ], 28 | '/': [ 29 | '', 30 | 'guide/', 31 | 'api/', 32 | 'examples/' 33 | ], 34 | }, 35 | searchPlaceholder: 'Search in docs...', 36 | repo: 'phrase/i18next-phrase-in-context-editor-post-processor', 37 | docsDir: 'docs', 38 | editLinks: true, 39 | editLinkText: 'Help us improve this page on GitHub', 40 | smoothScroll: true, 41 | displayAllHeaders: true, 42 | image: '/phrase-logo.jpg', 43 | domain: 'https://phrase.github.io/i18next-phrase-in-context-editor-post-processor' 44 | }, 45 | plugins: [ 46 | '@vuepress/last-updated', 47 | [ 48 | 'vuepress-plugin-clean-urls', 49 | { 50 | normalSuffix: '/', 51 | indexSuffix: '/', 52 | notFoundPath: '/404.html', 53 | }, 54 | ], 55 | [ 56 | 'seo', 57 | { 58 | title: ($page, $site) => $page.path === '/' ? $site.title : $page.title, 59 | description: ($page, $site) => $page.frontmatter.description || $site.description, 60 | type: $page => $page.path === '/' ? 'website' : 'article', 61 | image: ($page, $site) => { 62 | const image = $page.frontmatter.image || $site.themeConfig.image; 63 | return image && ((!image.startsWith('http') && $site.themeConfig.domain || '') + image) 64 | } 65 | }, 66 | ], 67 | [ 68 | () => ({ 69 | name: 'scroll-anchor-into-view-plugin', 70 | clientRootMixin: require('path').resolve(__dirname, 'mixins', 'scrollAnchorIntoView.js') 71 | }) 72 | ] 73 | ], 74 | } 75 | -------------------------------------------------------------------------------- /examples/nextjs/pages/index.js: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { i18n, withTranslation } from '../i18n'; 5 | 6 | function Home({ t }) { 7 | return ( 8 |
9 | 10 | Create Next App 11 | 12 | 13 | 14 |
15 |

16 | {t('title')} 17 |

18 | 19 |

20 | {t('intro')} 21 |

22 | 23 | 29 | 30 |
31 | 32 | 83 | 84 | 98 |
99 | ); 100 | } 101 | 102 | Home.getInitialProps = () => Promise.resolve({ 103 | namespacesRequired: ['common'], 104 | }); 105 | 106 | Home.propTypes = { 107 | t: PropTypes.func.isRequired, 108 | }; 109 | 110 | export default withTranslation('common')(Home); 111 | -------------------------------------------------------------------------------- /docs/api/index.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | ## PhraseInContextEditorPostProcessor 4 | 5 | The `PhraseInContextEditorPostProcessor` class is the main point of the **i18next Phrase In-Context Editor Post Processor** library and the only place where [**Phrase**](https://phrase.com)-specific logic happens. Implements [ i18n type](https://www.i18next.com/misc/creating-own-plugins#post-processor). 6 | 7 | ### Constructor 8 | 9 | _constructor_( 10 | **options**: [](#phraseincontexteditoroptions) 11 | ) 12 | 13 | ::: tip 14 | **options** argument is assigned to the component using [`config setter`](#config-setter) 15 | ::: 16 | 17 | 18 | 19 | 20 | ### Properties 21 | 22 | - #### config: [](#phraseconfig) 23 | 24 | - #### phraseEnabled: - property controlling the status of the In-Context Editor. Propagates the change to `globalThis.PHRASEAPP_ENABLED`. 25 | 26 | ### Methods 27 | 28 | - _interpolateKey_( 29 | **key**: , 30 | ): 31 | 32 | - _process_( 33 | **value**: , 34 | **keys**: , 35 | **options**: , 36 | **translator**: , 37 | ): 38 | 39 | ::: warning 40 | If the **In-Context Editor** is not yet loaded, changing `phraseEnabled` to `true` will trigger loading of the minified js source of the **In-Context Editor**. 41 | ::: 42 | 43 | :::tip 44 | Whenever disabled, the **i18next Phrase In-Context Editor Post Processor** will stop interpolating keys - this will revert back to the regular [**i18next**](https://www.i18next.com/) behavior. For more information on how the formatters are being handled, please head to the [introduction](../guide/). 45 | ::: 46 | 47 | ## PhraseInContextEditorOptions 48 | 49 | Partial type of the [](#phraseconfig) with only 2 properties required: `phraseEnabled` and `projectId`. 50 | 51 | ## PhraseConfig 52 | 53 | Type describing all possible **Phrase In-Context Editor** config options. 54 | 55 | ```typescript 56 | phraseEnabled: boolean; 57 | baseUrl: string; 58 | profileUrl: string; 59 | apiBaseUrl: string; 60 | oauthEndpointUrl: string; 61 | helpUrl: string; 62 | logoUrl: string; 63 | stylesheetUrl: string; 64 | version: string; 65 | priorityLocales: string[]; 66 | projectId: string; 67 | branch: string; 68 | ajaxObserver: boolean; 69 | debugMode: boolean; 70 | prefix: string; // default: '{{__' 71 | suffix: string; // default: '__}}' 72 | autoLowercase: boolean; 73 | forceLocale: boolean; 74 | loginDialogMessage: string; 75 | useOldICE: boolean; 76 | autoLogin: { 77 | perform: boolean; 78 | email: string; 79 | password: string; 80 | }; 81 | sso: { 82 | enabled: boolean; 83 | enforced: boolean; 84 | provider: string; 85 | identifier: string; 86 | }; 87 | fullReparse: boolean // default: true; 88 | origin: string; 89 | hidingClasses: string[]; // default: ['hidden', 'visuallyhidden', 'invisible']; 90 | ``` 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # i18next Phrase In-Context Editor Post Processor 2 | 3 | [![NPM version](https://img.shields.io/npm/v/i18next-phrase-in-context-editor-post-processor)](https://www.npmjs.com/package/i18next-phrase-in-context-editor-post-processor) 4 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 5 | [![LICENSE MIT](https://img.shields.io/github/license/phrase/i18next-phrase-in-context-editor-post-processor)](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/blob/master/LICENSE) 6 | [![GitHub documentation deployment](https://img.shields.io/github/deployments/phrase/i18next-phrase-in-context-editor-post-processor/github-pages?label=docs-deploy)](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/deployments?environment=github-pages) 7 | 8 | **i18next Phrase In-Context Editor Post Processor** is the official library for integrating [Phrase Strings In-Context Editor](https://support.phrase.com/hc/en-us/articles/5784095916188-In-Context-Editor-Strings) with [i18next](https://www.i18next.com/). 9 | 10 | ## :scroll: Documentation 11 | 12 | To get started, have a look at the [documentation](https://phrase.github.io/i18next-phrase-in-context-editor-post-processor/). There you will find [API docs](https://phrase.github.io/i18next-phrase-in-context-editor-post-processor/api/), [getting started guide](https://phrase.github.io/i18next-phrase-in-context-editor-post-processor/guide/), [examples](https://phrase.github.io/i18next-phrase-in-context-editor-post-processor/examples/) and much more! 13 | 14 | ## :hammer: Development 15 | 16 | ``` bash 17 | # install deps 18 | yarn 19 | 20 | # build dist files 21 | yarn build 22 | 23 | # serve examples at localhost:8080 24 | cd examples/basic 25 | yarn 26 | yarn start 27 | 28 | # lint & fix files 29 | yarn lint 30 | yarn lint.fix 31 | 32 | # run all tests 33 | yarn test 34 | yarn test.coverage 35 | 36 | # serve docs at localhost:8080/i18next-phrase-in-context-editor-post-processor/ 37 | yarn docs.dev 38 | ``` 39 | 40 | ## :white_check_mark: Commits & Pull Requests 41 | 42 | We welcome anyone who wants to contribute to our codebase, so if you notice something, feel free to open a Pull Request! However, we ask that you please use the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification for your commit messages and titles when opening a Pull Request. 43 | 44 | Example: `chore: Update README` 45 | 46 | We have an automated version management and release process based off of this message convention, so it'd be highly appreciated if you could follow this coding guideline. 47 | 48 | ## :question: Issues, Questions, Support 49 | 50 | Please use [GitHub issues](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/issues) to share your problem, and we will do our best to answer any questions or to support you in finding a solution! 51 | 52 | ## :memo: Changelog 53 | 54 | Detailed changes for each release are documented in the [changelog](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/blob/master/CHANGELOG.md). 55 | 56 | ## :package: Releasing 57 | 58 | Package is being released automatically with the use of [Semantic Release Action](https://github.com/marketplace/actions/action-for-semantic-release). Just push a commit to the master and a new version will be released right away! 59 | 60 | ## :copyright: License 61 | 62 | [MIT](http://opensource.org/licenses/MIT) 63 | -------------------------------------------------------------------------------- /src/phrase.ts: -------------------------------------------------------------------------------- 1 | import '@sagi.io/globalthis'; 2 | import './typings/global'; 3 | import './typings/helpers'; 4 | import { DeepPartial } from './typings/helpers'; 5 | import { PhraseConfig } from "./phrase-config.type"; 6 | 7 | export type PhraseInContextEditorOptions = Omit, 'phraseEnabled' | 'projectId'> & Pick; 8 | 9 | export default class PhraseInContextEditorPostProcessor { 10 | private defaultConfig: Partial = { 11 | prefix: '{{__', 12 | suffix: '__}}', 13 | fullReparse: true, 14 | useOldICE: false, 15 | scriptAutoLoad: true, 16 | origin: 'i18next-phrase-in-context-editor-post-processor', 17 | }; 18 | phraseScript?: HTMLScriptElement; 19 | 20 | get IN_CONTEXT_EDITOR_SCRIPT_URL () { 21 | if (this.config.useOldICE) { 22 | return `https://phrase.com/assets/in-context-editor/2.0/app.js?${new Date().getTime()}`; 23 | } else { 24 | return 'https://cdn.phrase.com/strings/plugins/editor/latest/ice/index.js'; 25 | } 26 | } 27 | 28 | static interpolateKey (key: string, prefix: string, suffix: string): string { 29 | return prefix + 'phrase_' + key + suffix; 30 | } 31 | 32 | loadInContextEditorScript() { 33 | if (typeof window !== 'undefined') { 34 | const phraseScript = document.createElement('script'); 35 | 36 | !this.config.useOldICE 37 | ? phraseScript.type = 'module' 38 | : phraseScript.type = 'text/javascript'; 39 | 40 | phraseScript.async = true; 41 | phraseScript.src = this.IN_CONTEXT_EDITOR_SCRIPT_URL; 42 | const script = document.getElementsByTagName('script')[0]; 43 | if (script && script.parentNode) { 44 | script.parentNode.insertBefore(phraseScript, script); 45 | } else { 46 | document.body.appendChild(phraseScript); 47 | } 48 | 49 | return phraseScript; 50 | } 51 | } 52 | 53 | type: 'postProcessor' = 'postProcessor'; 54 | name = 'phraseInContextEditor'; 55 | 56 | constructor (options: PhraseInContextEditorOptions) { 57 | this.config = { ...globalThis.PHRASEAPP_CONFIG, ...options } as PhraseConfig; 58 | this.phraseEnabled = options.phraseEnabled; 59 | } 60 | 61 | interpolateKey (key: string) { 62 | return PhraseInContextEditorPostProcessor.interpolateKey(key, this.config.prefix, this.config.suffix); 63 | } 64 | 65 | process (value: string, keys: string[], options: unknown, translator: unknown): string { 66 | return this.phraseEnabled 67 | ? this.interpolateKey(keys[0]) 68 | : value; 69 | } 70 | 71 | set phraseEnabled(phraseEnabled: boolean) { 72 | globalThis.PHRASEAPP_ENABLED = phraseEnabled; 73 | if (phraseEnabled && this.config.scriptAutoLoad && !this.phraseScript) { 74 | this.phraseScript = this.loadInContextEditorScript(); 75 | } 76 | } 77 | 78 | get phraseEnabled() { 79 | return globalThis.PHRASEAPP_ENABLED; 80 | } 81 | 82 | set config(options: PhraseConfig) { 83 | globalThis.PHRASEAPP_CONFIG = { ...this.defaultConfig, ...options }; 84 | } 85 | 86 | get config() { 87 | return globalThis.PHRASEAPP_CONFIG; 88 | } 89 | 90 | toScriptHTML () { 91 | return ``; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /docs/guide/index.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | ## Compatibility 4 | 5 | ::: warning 6 | **i18next Phrase In-Context Editor Post Processor** supports all **i18next** versions newer than [`19.5.1`](https://github.com/i18next/i18next/releases/tag/v19.5.1). Although this library might work with previous versions as well, they're not officialy supported and might not get any specific updates or bug fixes. 7 | ::: 8 | 9 | ## Installation 10 | 11 | You can install the package with the use of your favorite package manager: 12 | 13 | ```bash 14 | # yarn 15 | yarn add i18next-phrase-in-context-editor-post-processor 16 | 17 | # npm 18 | npm install i18next-phrase-in-context-editor-post-processor 19 | ``` 20 | 21 | or load it via CDN: 22 | 23 | ```html 24 | 25 | ``` 26 | 27 | ## Usage 28 | 29 | Let's say this is how you bootstrap [**i18next**](https://www.i18next.com/) in your application: 30 | 31 | ```typescript 32 | import i18next from 'i18next'; 33 | import i18nextXHRBackend from 'i18next-xhr-backend'; 34 | 35 | i18next 36 | .use(i18nextXHRBackend) 37 | .init({ 38 | fallbackLng: 'en', 39 | debug: true, 40 | ns: ['special', 'common'], 41 | defaultNS: 'special', 42 | backend: { 43 | // load some translations from i18next-gitbook repo 44 | loadPath: 'https://raw.githubusercontent.com/i18next/i18next-gitbook/master/locales/{{lng}}/{{ns}}.json', 45 | crossDomain: true 46 | } 47 | }, function(err, t) { 48 | // do something on i18next initialization 49 | }); 50 | ``` 51 | 52 | In this case, to integrate our library you only need to follow four simple steps: 53 | 54 | - Import a **i18next Phrase In-Context Editor Post Processor** library. 55 | - Create new **i18next Phrase In-Context Editor Post Processor** instance, passing [](../api#phraseconfig) as the argument. 56 | - Pass newly created **i18next Phrase In-Context Editor Post Processor** instance to **i18next** [`use method`](https://www.i18next.com/overview/api#use). 57 | - Add the `"phraseInContextEditor"` to [`postProcess`](https://www.i18next.com/overview/configuration-options#translation-defaults) array property (passed within the configuration object of **i18next** [`init method`](https://www.i18next.com/overview/api#init)). 58 | 59 | Sounds easy enough, right? Let's have a look at an updated example: 60 | 61 | ```typescript{3,7-10,21} 62 | import i18next from 'i18next'; 63 | import i18nextXHRBackend from 'i18next-xhr-backend'; 64 | import PhraseInContextEditorPostProcessor from 'i18next-phrase-in-context-editor-post-processor'; 65 | 66 | i18next 67 | .use(i18nextXHRBackend) 68 | .use(new PhraseInContextEditorPostProcessor({ 69 | phraseEnabled: true, 70 | projectId: '' // Project ID is found in project settings. 71 | accountId: '' // Account ID is found in Strings organization settings. 72 | })) 73 | .init({ 74 | fallbackLng: 'en', 75 | debug: true, 76 | ns: ['special', 'common'], 77 | defaultNS: 'special', 78 | backend: { 79 | // load some translations from i18next-gitbook repo 80 | loadPath: 'https://raw.githubusercontent.com/i18next/i18next-gitbook/master/locales/{{lng}}/{{ns}}.json', 81 | crossDomain: true 82 | }, 83 | postProcess: ['phraseInContextEditor'] 84 | }, function(err, t) { 85 | // do something on i18next initialization 86 | }); 87 | ``` 88 | 89 | From now on, the **In-Context Editor** is fully integrated into your JS app. Congratulations! :tada: 90 | 91 | To use the old version of ICE, use option `useOldICE: true` in your PHRASEAPP_CONFIG or integration options 92 | ```js 93 | i18n 94 | .use(new PhraseInContextEditorPostProcessor({ 95 | phraseEnabled: true, 96 | projectId: '', 97 | accountId: '', 98 | useOldICE: true, 99 | })) 100 | ``` 101 | 102 | For further information about the possible configuration options and useful methods, please have a look at our [API docs](../../api). 103 | 104 | ## Using the US Datacenter with ICE 105 | 106 | In addition to the settings in your config, set the US datacenter to enable it working with the US endpoints. 107 | ```js 108 | datacenter: 'us' 109 | ``` 110 | -------------------------------------------------------------------------------- /docs/examples/index.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Here is a collection of example applications using the **i18next Phrase In-Context Editor Post Processor** library. 4 | 5 | ## Basic (Vanilla js) 6 | 7 | This application features basic functionalities of the [**i18next**](https://www.i18next.com/) and the [**Phrase In-Context Editor**](https://help.phrase.com/help/translate-directly-on-your-website). 8 | 9 | Go to, and clone [`examples/basic` directory of **i18next Phrase In-Context Editor Post Processor** repo](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/tree/master/examples/basic). 10 | 11 | To run the example use following commands: 12 | 13 | ```bash 14 | # yarn 15 | yarn 16 | yarn start 17 | 18 | # npm 19 | npm i 20 | npm run start 21 | ``` 22 | 23 | Now the application is visible under [http://localhost:1234/](http://localhost:1234/). 24 | 25 | This application uses a demo [Phrase](https://phrase.com) account under the hood. To login, use authentication data provided below: 26 | 27 | ```bash 28 | E-Mail: demo@phrase.com 29 | Password: phrase 30 | ``` 31 | 32 | ## React i18next 33 | 34 | This application features basic functionalities of the [**react-i18next**](https://react.i18next.com) and the [**Phrase In-Context Editor**](https://help.phrase.com/help/translate-directly-on-your-website). 35 | 36 | Go to, and clone [`examples/react` directory of **i18next Phrase In-Context Editor Post Processor** repo](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/tree/master/examples/react). 37 | 38 | To run the example use following commands: 39 | 40 | ```bash 41 | # yarn 42 | yarn 43 | yarn start 44 | 45 | # npm 46 | npm i 47 | npm run start 48 | ``` 49 | 50 | Now the application is visible under [http://localhost:3000/](http://localhost:3000/). 51 | 52 | The page will reload if you make edits. 53 | Any lint errors will be visible in the console. 54 | 55 | This application uses a demo [Phrase](https://phrase.com) account under the hood. To login, use authentication data provided below: 56 | 57 | ```bash 58 | E-Mail: demo@phrase.com 59 | Password: phrase 60 | ``` 61 | 62 | ## Vue i18next 63 | 64 | This application features basic functionalities of the [**@panter/vue-i18next**](https://panter.github.io/vue-i18next/) and the [**Phrase In-Context Editor**](https://help.phrase.com/help/translate-directly-on-your-website). 65 | 66 | Go to, and clone [`examples/vue` directory of **i18next Phrase In-Context Editor Post Processor** repo](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/tree/master/examples/vue). 67 | 68 | To run the example, use following commands: 69 | 70 | ```bash 71 | # yarn 72 | yarn 73 | yarn serve 74 | 75 | # npm 76 | npm i 77 | npm run serve 78 | ``` 79 | 80 | Now the application will be visible under [http://localhost:8080/](http://localhost:8080/). 81 | 82 | The page will reload if you make edits. 83 | Any lint errors will be visible in the console. 84 | 85 | This application uses a demo [Phrase](https://phrase.com) account under the hood. To login, use authentication data provided below: 86 | 87 | ```bash 88 | E-Mail: demo@phrase.com 89 | Password: phrase 90 | ``` 91 | 92 | ::: tip Notice 93 | This demo was built with the use of [Vue CLI](https://cli.vuejs.org). For more details, see the [Configuration Reference](https://cli.vuejs.org/config/). 94 | ::: 95 | 96 | ## Next.js i18next 97 | 98 | This application features basic functionalities of the [**nextjs-i18next**](https://www.npmjs.com/package/next-i18next) and the [**Phrase In-Context Editor**](https://help.phrase.com/help/translate-directly-on-your-website). 99 | 100 | Go to, and clone [`examples/next` directory of **i18next Phrase In-Context Editor Post Processor** repo](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/tree/master/examples/nextjs). 101 | 102 | To run the example use following commands: 103 | 104 | ```bash 105 | # yarn 106 | yarn 107 | yarn dev 108 | 109 | # npm 110 | npm i 111 | npm run dev 112 | ``` 113 | 114 | Now the application is visible under [http://localhost:3000/](http://localhost:3000/). 115 | 116 | The page will reload if you make edits. 117 | Any nextjs errors will be visible within console. 118 | 119 | This application uses a demo [Phrase](https://phrase.com) account under the hood. To login, use authentication data provided below: 120 | 121 | ```bash 122 | E-Mail: demo@phrase.com 123 | Password: phrase 124 | ``` 125 | 126 | ::: tip Notice 127 | This demo was built on top of the starter template from [Learn Next.js guide](https://nextjs.org/learn) and the [next-i18next simple example project](https://github.com/isaachinman/next-i18next/tree/master/examples/simple). 128 | ::: 129 | 130 | ## I18next http middleware (Express.js) 131 | 132 | This application features basic functionalities of the [**i18next-http-middleware**](https://www.npmjs.com/package/i18next-http-middleware) and the [**Phrase In-Context Editor**](https://help.phrase.com/help/translate-directly-on-your-website). 133 | 134 | Go to, and clone [`examples/express-basic` directory of **i18next Phrase In-Context Editor Post Processor** repo](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/tree/master/examples/express-basic). 135 | 136 | To run the example use following commands: 137 | 138 | ```bash 139 | # yarn 140 | yarn 141 | yarn start 142 | 143 | # npm 144 | npm i 145 | npm run start 146 | ``` 147 | 148 | Now the application is visible under [http://localhost:8080/](http://localhost:8080/). 149 | 150 | You need to rerun `yarn start` command every time you've made any change to the server's code. 151 | Any Express errors will be visible within console. 152 | 153 | This application uses a demo [Phrase](https://phrase.com) account under the hood. To login, use authentication data provided below: 154 | 155 | ```bash 156 | E-Mail: demo@phrase.com 157 | Password: phrase 158 | ``` 159 | 160 | ::: tip Notice 161 | This demo was built on top of the basic example from [i18next-http-middleware repository](https://github.com/i18next/i18next-http-middleware/tree/master/example/basic). 162 | ::: 163 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.7.2](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.7.1...v1.7.2) (2025-08-25) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * **SCM-659:** Add new option for toggling off sentry and mixpanel ([#223](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/issues/223)) ([e7ff3f2](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/e7ff3f21b51b342a494fb4307132d164de554602)) 7 | 8 | ## [1.7.1](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.7.0...v1.7.1) (2025-08-04) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * **deps:** bump rollup from 2.79.1 to 2.79.2 ([#207](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/issues/207)) ([a836a2c](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/a836a2c0c80757cbbe3b60b244d8328a17ad361b)) 14 | 15 | # [1.7.0](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.6.1...v1.7.0) (2024-12-12) 16 | 17 | 18 | ### Features 19 | 20 | * **STRINGS-221:** Extend `PhraseConfig` type with `hidingClasses` field ([#216](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/issues/216)) ([f9d6562](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/f9d6562d1d97c750cd4b0956bef857bd45b1d4b5)) 21 | 22 | ## [1.6.1](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.6.0...v1.6.1) (2023-12-05) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * Add i18next ^23.0.0 peer dependency ([#169](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/issues/169)) ([7bb75d5](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/7bb75d58c89339feaf490256439a9c2d7745208f)) 28 | 29 | # [1.6.0](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.5.2...v1.6.0) (2023-11-27) 30 | 31 | 32 | ### Features 33 | 34 | * **TSE-1350:** Add origin field to ICE config ([#168](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/issues/168)) ([6fa1218](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/6fa12185a7e3140085b7781bbe92af3b4d379deb)) 35 | 36 | ## [1.5.2](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.5.1...v1.5.2) (2023-08-28) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * **TSE-1157:** Improve US datacenter documentation and typings ([#146](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/issues/146)) ([80d091d](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/80d091d8eaf3cea9feea4ce33017ffdd0f4d2e5a)) 42 | 43 | ## [1.5.1](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.5.0...v1.5.1) (2023-05-25) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * Add i18next ^22.0.0 peer dependency ([4f05bf1](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/4f05bf1690ddb7ef215af89d036ba779425ed24f)) 49 | 50 | # [1.5.0](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.4.1...v1.5.0) (2023-05-24) 51 | 52 | 53 | ### Features 54 | 55 | * Use new ICE by default ([6d6d30b](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/6d6d30bc13f791da3e141fa0e51ad9ad5f454919)) 56 | 57 | ## [1.4.1](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.4.0...v1.4.1) (2023-05-23) 58 | 59 | 60 | ### Bug Fixes 61 | 62 | * add workaround for docs to run ([54cb3cc](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/54cb3cced8ce52a5445853c4fcf0a42d8f6daf15)) 63 | 64 | # [1.4.0](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.3.0...v1.4.0) (2023-05-12) 65 | 66 | 67 | ### Features 68 | 69 | * Use new in-context-editor by default ([2a6a5ea](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/2a6a5ea3c7aa041f14ead3e8f89c94a110cddc5c)) 70 | 71 | # [1.3.0](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.2.1...v1.3.0) (2021-12-14) 72 | 73 | 74 | ### Features 75 | 76 | * **i18next:** add v20, 21 as valid peer deps ([9d1d87f](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/9d1d87fc4adfda8596bcb291caf3bf68af7317f2)), closes [#69](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/issues/69) 77 | 78 | ## [1.2.1](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.2.0...v1.2.1) (2021-02-15) 79 | 80 | 81 | ### Bug Fixes 82 | 83 | * add helpers.d.t file to the output bundle ([10984df](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/10984dfaca3b24dd92e5c523749e524bcb843edd)), closes [#15](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/issues/15) 84 | 85 | # [1.2.0](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.1.1...v1.2.0) (2021-02-15) 86 | 87 | 88 | ### Features 89 | 90 | * upgrade nextjs example to next@10 ([ceabb7f](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/ceabb7f314bf2b4fcf882fe03f28400fcc5f8c9e)) 91 | 92 | ## [1.1.1](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.1.0...v1.1.1) (2021-01-07) 93 | 94 | 95 | ### Bug Fixes 96 | 97 | * server-side-rendering works with next.js ([#14](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/issues/14)) ([7cd9ad6](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/7cd9ad6136d0bb9a517a64e7677e2c870dfece25)) 98 | 99 | # [1.1.0](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/compare/v1.0.0...v1.1.0) (2020-07-20) 100 | 101 | 102 | ### Features 103 | 104 | * possibility to disable script autoLoad func ([#3](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/issues/3)) ([2443f4b](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/2443f4bb904a03e710ebec5b268a74f95e27e090)) 105 | 106 | # 1.0.0 (2020-07-01) 107 | 108 | 109 | ### Features 110 | 111 | * initial commit ([1ffd038](https://github.com/phrase/i18next-phrase-in-context-editor-post-processor/commit/1ffd03891067d3c3e97d80827d1548d45ef022be)) 112 | -------------------------------------------------------------------------------- /tests/phrase.spec.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-non-null-assertion */ 2 | import PhraseInContextEditorPostProcessor, { PhraseInContextEditorOptions } from "../src/phrase"; 3 | 4 | let phraseInContextEditorPostProcessor: PhraseInContextEditorPostProcessor | undefined; 5 | let phraseScript: HTMLScriptElement | null; 6 | 7 | const ICEOptions = { 8 | phraseEnabled: true, 9 | } as PhraseInContextEditorOptions; 10 | 11 | const createPhraseInContextEditorPostProcessor = () => { 12 | phraseInContextEditorPostProcessor = new PhraseInContextEditorPostProcessor(ICEOptions); 13 | }; 14 | 15 | beforeEach(() => { 16 | phraseScript = null; 17 | phraseInContextEditorPostProcessor = undefined; 18 | ICEOptions.phraseEnabled = true; 19 | ICEOptions.useOldICE = false; 20 | }); 21 | 22 | afterEach(() => { 23 | document.head.innerHTML = ''; 24 | document.body.innerHTML = ''; 25 | }); 26 | 27 | describe('constructor', () => { 28 | describe('when phraseEnabled = true', () => { 29 | describe('when using the new in context editor', () => { 30 | beforeEach(() => { 31 | ICEOptions.useOldICE = false; 32 | createPhraseInContextEditorPostProcessor(); 33 | phraseScript = document.querySelector('script'); 34 | }); 35 | 36 | it('should add script tag to the document', () => { 37 | expect(phraseScript).not.toBeNull(); 38 | }); 39 | it('should add script tag with phrase url', () => { 40 | expect(phraseScript?.src).toBe('https://cdn.phrase.com/strings/plugins/editor/latest/ice/index.js'); 41 | }); 42 | it('should set window.PHRASEAPP_ENABLED', () => { 43 | expect(window.PHRASEAPP_ENABLED).toBeTruthy(); 44 | }); 45 | it('should set origin', () => { 46 | expect(window.PHRASEAPP_CONFIG.origin).toBe('i18next-phrase-in-context-editor-post-processor'); 47 | }); 48 | describe('when script element already exists in the document', () => { 49 | let script: HTMLScriptElement; 50 | beforeEach(() => { 51 | script = document.createElement('script'); 52 | document.head.append(script); 53 | createPhraseInContextEditorPostProcessor(); 54 | phraseScript = document.querySelector('script'); 55 | }); 56 | 57 | it('should add phrase script right before the first script element', () => { 58 | expect(phraseScript?.nextElementSibling).toBe(script); 59 | }); 60 | }); 61 | }); 62 | 63 | describe('when using the old in context editor', () => { 64 | beforeEach(() => { 65 | ICEOptions.useOldICE = true; 66 | createPhraseInContextEditorPostProcessor(); 67 | phraseScript = document.querySelector('script'); 68 | }); 69 | 70 | it('should add script tag to the document', () => { 71 | expect(phraseScript).not.toBeNull(); 72 | }); 73 | it('should add script tag with phrase url', () => { 74 | expect(phraseScript?.src.split('?')[0]).toBe('https://phrase.com/assets/in-context-editor/2.0/app.js'.split('?')[0]); 75 | }); 76 | it('should set window.PHRASEAPP_ENABLED', () => { 77 | expect(window.PHRASEAPP_ENABLED).toBeTruthy(); 78 | }); 79 | describe('when script element already exists in the document', () => { 80 | let script: HTMLScriptElement; 81 | beforeEach(() => { 82 | script = document.createElement('script'); 83 | document.head.append(script); 84 | createPhraseInContextEditorPostProcessor(); 85 | phraseScript = document.querySelector('script'); 86 | }); 87 | 88 | it('should add phrase script right before the first script element', () => { 89 | expect(phraseScript?.nextElementSibling).toBe(script); 90 | }); 91 | }); 92 | }); 93 | }); 94 | 95 | describe('when phraseEnabled = false', () => { 96 | beforeEach(() => { 97 | ICEOptions.phraseEnabled = false; 98 | createPhraseInContextEditorPostProcessor(); 99 | phraseScript = document.querySelector('script'); 100 | }); 101 | 102 | it('should not add script tag to the document', () => { 103 | expect(phraseScript).toBeNull(); 104 | }); 105 | it('should set window.PHRASEAPP_ENABLED', () => { 106 | expect(window.PHRASEAPP_ENABLED).toBeFalsy(); 107 | }); 108 | }); 109 | }); 110 | 111 | describe('::interpolateKey', () => { 112 | it('should add prefix & suffix correctly', () => { 113 | createPhraseInContextEditorPostProcessor(); 114 | expect(PhraseInContextEditorPostProcessor.interpolateKey('some.key', 'prefix-', '-suffix')).toBe('prefix-phrase_some.key-suffix'); 115 | }); 116 | }); 117 | 118 | describe('::loadInContextEditorScript', () => { 119 | it('should add script element at the end of the body', () => { 120 | createPhraseInContextEditorPostProcessor(); 121 | expect(document.body.children[document.body.children.length-1].tagName).toBe('SCRIPT'); 122 | }); 123 | 124 | describe('when there is already script element in DOM', () => { 125 | beforeEach(() => { 126 | document.head.append(document.createElement('script')); 127 | createPhraseInContextEditorPostProcessor(); 128 | }); 129 | 130 | it('should add script element before first script tag', () => { 131 | const loadedScript = document.querySelector('script'); 132 | expect(loadedScript?.src.split('?')[0]).toBe(phraseInContextEditorPostProcessor!.IN_CONTEXT_EDITOR_SCRIPT_URL.split('?')[0]); 133 | expect(document.querySelectorAll('script').length).toBe(2); 134 | }); 135 | }); 136 | 137 | describe('when window is undefined for SSR', () => { 138 | const { window } = global; 139 | 140 | beforeAll(() => { 141 | delete (global).window; 142 | }); 143 | 144 | it('runs without errors', () => { 145 | createPhraseInContextEditorPostProcessor(); 146 | expect(phraseInContextEditorPostProcessor!.loadInContextEditorScript).not.toThrow(); 147 | }); 148 | 149 | afterAll(() => { 150 | global.window = window; 151 | }); 152 | }); 153 | }); 154 | 155 | describe('process', () => { 156 | describe('when phraseEnabled = true', () => { 157 | beforeEach(() => { 158 | ICEOptions.prefix = 'prefix-'; 159 | ICEOptions.suffix = '-suffix'; 160 | createPhraseInContextEditorPostProcessor(); 161 | }); 162 | 163 | it('should return the interpolated key', () => { 164 | expect(phraseInContextEditorPostProcessor!.process('value', ['some.key'], null, null)).toBe('prefix-phrase_some.key-suffix'); 165 | }); 166 | }); 167 | 168 | describe('when phraseEnabled = false', () => { 169 | it('should return translated value', () => { 170 | ICEOptions.phraseEnabled = false; 171 | createPhraseInContextEditorPostProcessor(); 172 | expect(phraseInContextEditorPostProcessor!.process('value', ['some.key'], null, null)).toBe('value'); 173 | }); 174 | }); 175 | }); 176 | 177 | describe('interpolateKey', () => { 178 | beforeEach(() => { 179 | ICEOptions.prefix = 'prefix-'; 180 | ICEOptions.suffix = '-suffix'; 181 | createPhraseInContextEditorPostProcessor(); 182 | }); 183 | 184 | it('should add prefix & suffix correctly', () => { 185 | expect(phraseInContextEditorPostProcessor!.interpolateKey('some.key')).toBe('prefix-phrase_some.key-suffix'); 186 | }); 187 | }); 188 | 189 | describe('phraseEnabled setter', () => { 190 | let PhraseInContextEditorPostProcessorLoadInContextEditorScriptSpy: jest.SpyInstance; 191 | 192 | describe('when phrase was enabled initially', () => { 193 | beforeEach(() => { 194 | createPhraseInContextEditorPostProcessor(); 195 | PhraseInContextEditorPostProcessorLoadInContextEditorScriptSpy = jest.spyOn( 196 | phraseInContextEditorPostProcessor!, 197 | 'loadInContextEditorScript' 198 | ).mockReturnValue(document.createElement('script')); 199 | }); 200 | 201 | it('should call loadInContextEditorScript if script is missing', () => { 202 | // First call already done in constructor, check if it will trigger again when toggling on and off 203 | phraseInContextEditorPostProcessor!.phraseEnabled = false; 204 | phraseInContextEditorPostProcessor!.phraseScript = undefined; 205 | phraseInContextEditorPostProcessor!.phraseEnabled = true; 206 | expect(PhraseInContextEditorPostProcessorLoadInContextEditorScriptSpy).toBeCalledTimes(1); 207 | }); 208 | 209 | it('does not load script multiple times', () => { 210 | // First call already done in constructor, check if it will trigger again when toggling on and off 211 | phraseInContextEditorPostProcessor!.phraseEnabled = false; 212 | phraseInContextEditorPostProcessor!.phraseEnabled = true; 213 | expect(PhraseInContextEditorPostProcessorLoadInContextEditorScriptSpy).toBeCalledTimes(0); 214 | }); 215 | }); 216 | }); 217 | 218 | describe('phraseEnabled getter', () => { 219 | it('should return phraseEnabled correctly', () => { 220 | createPhraseInContextEditorPostProcessor(); 221 | expect(phraseInContextEditorPostProcessor!.phraseEnabled).toBeTruthy(); 222 | phraseInContextEditorPostProcessor!.phraseEnabled = false; 223 | expect(phraseInContextEditorPostProcessor!.phraseEnabled).toBeFalsy(); 224 | }); 225 | }); 226 | 227 | describe('config getter', () => { 228 | it('should return PHRASEAPP_CONFIG GLOBAL', () => { 229 | createPhraseInContextEditorPostProcessor(); 230 | expect(phraseInContextEditorPostProcessor!.config).toBe(global.PHRASEAPP_CONFIG); 231 | }); 232 | }); 233 | 234 | describe('toScriptHTML', () => { 235 | describe('when using the new in context editor', () => { 236 | beforeEach(() => { 237 | ICEOptions.useOldICE = false; 238 | createPhraseInContextEditorPostProcessor(); 239 | document.body.innerHTML = phraseInContextEditorPostProcessor!.toScriptHTML(); 240 | }); 241 | 242 | it('should a valid script HTML with editor url in place', () => { 243 | const scripts = document.querySelectorAll('script'); 244 | expect(scripts.length).toBe(2); 245 | expect(scripts[1].src.split('?')[0]).toBe('https://cdn.phrase.com/strings/plugins/editor/latest/ice/index.js'.split('?')[0]); 246 | }); 247 | }); 248 | 249 | describe('when using the old in context editor', () => { 250 | beforeEach(() => { 251 | ICEOptions.useOldICE = true; 252 | createPhraseInContextEditorPostProcessor(); 253 | document.body.innerHTML = phraseInContextEditorPostProcessor!.toScriptHTML(); 254 | }); 255 | 256 | it('should a valid script HTML with editor url in place', () => { 257 | const scripts = document.querySelectorAll('script'); 258 | expect(scripts.length).toBe(2); 259 | expect(scripts[1].src.split('?')[0]).toBe('https://phrase.com/assets/in-context-editor/2.0/app.js'.split('?')[0]); 260 | }); 261 | }); 262 | }); 263 | -------------------------------------------------------------------------------- /examples/express-basic/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/runtime@^7.3.1": 6 | version "7.12.13" 7 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d" 8 | integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw== 9 | dependencies: 10 | regenerator-runtime "^0.13.4" 11 | 12 | "@sagi.io/globalthis@^0.0.2": 13 | version "0.0.2" 14 | resolved "https://registry.yarnpkg.com/@sagi.io/globalthis/-/globalthis-0.0.2.tgz#2777ab71721ecaab1b0dd8aa75abbe0f3ae1ba58" 15 | integrity sha512-sazB1BcX8IK5UxDgEEd0J1bPB0rwH0k725nYva6ofBhvNX8lMytuRf3mlkTkytNHKTi4EGxBOY+YAD3PkeEtlA== 16 | 17 | accepts@~1.3.7: 18 | version "1.3.7" 19 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" 20 | integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== 21 | dependencies: 22 | mime-types "~2.1.24" 23 | negotiator "0.6.2" 24 | 25 | array-flatten@1.1.1: 26 | version "1.1.1" 27 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 28 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= 29 | 30 | body-parser@1.19.0: 31 | version "1.19.0" 32 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" 33 | integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== 34 | dependencies: 35 | bytes "3.1.0" 36 | content-type "~1.0.4" 37 | debug "2.6.9" 38 | depd "~1.1.2" 39 | http-errors "1.7.2" 40 | iconv-lite "0.4.24" 41 | on-finished "~2.3.0" 42 | qs "6.7.0" 43 | raw-body "2.4.0" 44 | type-is "~1.6.17" 45 | 46 | bytes@3.1.0: 47 | version "3.1.0" 48 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" 49 | integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== 50 | 51 | content-disposition@0.5.3: 52 | version "0.5.3" 53 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" 54 | integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== 55 | dependencies: 56 | safe-buffer "5.1.2" 57 | 58 | content-type@~1.0.4: 59 | version "1.0.4" 60 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 61 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== 62 | 63 | cookie-signature@1.0.6: 64 | version "1.0.6" 65 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 66 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 67 | 68 | cookie@0.4.0: 69 | version "0.4.0" 70 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" 71 | integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== 72 | 73 | debug@2.6.9: 74 | version "2.6.9" 75 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 76 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 77 | dependencies: 78 | ms "2.0.0" 79 | 80 | depd@~1.1.2: 81 | version "1.1.2" 82 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 83 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 84 | 85 | destroy@~1.0.4: 86 | version "1.0.4" 87 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 88 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 89 | 90 | ee-first@1.1.1: 91 | version "1.1.1" 92 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 93 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 94 | 95 | encodeurl@~1.0.2: 96 | version "1.0.2" 97 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 98 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 99 | 100 | escape-html@~1.0.3: 101 | version "1.0.3" 102 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 103 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 104 | 105 | etag@~1.8.1: 106 | version "1.8.1" 107 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 108 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 109 | 110 | express@4.17.1: 111 | version "4.17.1" 112 | resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" 113 | integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== 114 | dependencies: 115 | accepts "~1.3.7" 116 | array-flatten "1.1.1" 117 | body-parser "1.19.0" 118 | content-disposition "0.5.3" 119 | content-type "~1.0.4" 120 | cookie "0.4.0" 121 | cookie-signature "1.0.6" 122 | debug "2.6.9" 123 | depd "~1.1.2" 124 | encodeurl "~1.0.2" 125 | escape-html "~1.0.3" 126 | etag "~1.8.1" 127 | finalhandler "~1.1.2" 128 | fresh "0.5.2" 129 | merge-descriptors "1.0.1" 130 | methods "~1.1.2" 131 | on-finished "~2.3.0" 132 | parseurl "~1.3.3" 133 | path-to-regexp "0.1.7" 134 | proxy-addr "~2.0.5" 135 | qs "6.7.0" 136 | range-parser "~1.2.1" 137 | safe-buffer "5.1.2" 138 | send "0.17.1" 139 | serve-static "1.14.1" 140 | setprototypeof "1.1.1" 141 | statuses "~1.5.0" 142 | type-is "~1.6.18" 143 | utils-merge "1.0.1" 144 | vary "~1.1.2" 145 | 146 | finalhandler@~1.1.2: 147 | version "1.1.2" 148 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" 149 | integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== 150 | dependencies: 151 | debug "2.6.9" 152 | encodeurl "~1.0.2" 153 | escape-html "~1.0.3" 154 | on-finished "~2.3.0" 155 | parseurl "~1.3.3" 156 | statuses "~1.5.0" 157 | unpipe "~1.0.0" 158 | 159 | forwarded@~0.1.2: 160 | version "0.1.2" 161 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 162 | integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= 163 | 164 | fresh@0.5.2: 165 | version "0.5.2" 166 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 167 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 168 | 169 | http-errors@1.7.2: 170 | version "1.7.2" 171 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" 172 | integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== 173 | dependencies: 174 | depd "~1.1.2" 175 | inherits "2.0.3" 176 | setprototypeof "1.1.1" 177 | statuses ">= 1.5.0 < 2" 178 | toidentifier "1.0.0" 179 | 180 | http-errors@~1.7.2: 181 | version "1.7.3" 182 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" 183 | integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== 184 | dependencies: 185 | depd "~1.1.2" 186 | inherits "2.0.4" 187 | setprototypeof "1.1.1" 188 | statuses ">= 1.5.0 < 2" 189 | toidentifier "1.0.0" 190 | 191 | i18next-fs-backend@1.0.2: 192 | version "1.0.2" 193 | resolved "https://registry.yarnpkg.com/i18next-fs-backend/-/i18next-fs-backend-1.0.2.tgz#8f137a59a7088c97d5112d6d624ebb8c5675fac9" 194 | integrity sha512-7N7V6gu00/UBdCjz37JFKGB5UfuLDJlIoLMAxXQG5ih3CFKPqtJ7GfQ7lqd8t/L/6EQ1sciZ9022Xy5BwTrf7g== 195 | 196 | i18next-http-middleware@1.0.1: 197 | version "1.0.1" 198 | resolved "https://registry.yarnpkg.com/i18next-http-middleware/-/i18next-http-middleware-1.0.1.tgz#ec113a41fcd208b4c4b22863ac98a536d48400db" 199 | integrity sha512-swo3FaOkKciv1KoROqG63qb6MVaE/2hp0xnBUIJAd8lTUJFSG6+ZepO33rMw/5zcMG6wnU1e+DWPXVt5UMxGJQ== 200 | 201 | "i18next-phrase-in-context-editor-post-processor@link:../..": 202 | version "0.0.0" 203 | uid "" 204 | 205 | i18next@19.4.4: 206 | version "19.4.4" 207 | resolved "https://registry.yarnpkg.com/i18next/-/i18next-19.4.4.tgz#c0a18bc2f2be554da636e67bfbf5200c7948b60d" 208 | integrity sha512-ofaHtdsDdX3A5nYur1HWblB7J4hIcjr2ACdnwTAJgc8hTfPbyzZfGX0hVkKpI3vzDIgO6Uzc4v1ffW2W6gG6zw== 209 | dependencies: 210 | "@babel/runtime" "^7.3.1" 211 | 212 | iconv-lite@0.4.24: 213 | version "0.4.24" 214 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 215 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 216 | dependencies: 217 | safer-buffer ">= 2.1.2 < 3" 218 | 219 | inherits@2.0.3: 220 | version "2.0.3" 221 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 222 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 223 | 224 | inherits@2.0.4: 225 | version "2.0.4" 226 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 227 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 228 | 229 | ipaddr.js@1.9.1: 230 | version "1.9.1" 231 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 232 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== 233 | 234 | media-typer@0.3.0: 235 | version "0.3.0" 236 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 237 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 238 | 239 | merge-descriptors@1.0.1: 240 | version "1.0.1" 241 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 242 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= 243 | 244 | methods@~1.1.2: 245 | version "1.1.2" 246 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 247 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 248 | 249 | mime-db@1.45.0: 250 | version "1.45.0" 251 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea" 252 | integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w== 253 | 254 | mime-types@~2.1.24: 255 | version "2.1.28" 256 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd" 257 | integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ== 258 | dependencies: 259 | mime-db "1.45.0" 260 | 261 | mime@1.6.0: 262 | version "1.6.0" 263 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 264 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 265 | 266 | ms@2.0.0: 267 | version "2.0.0" 268 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 269 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 270 | 271 | ms@2.1.1: 272 | version "2.1.1" 273 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 274 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== 275 | 276 | negotiator@0.6.2: 277 | version "0.6.2" 278 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" 279 | integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== 280 | 281 | on-finished@~2.3.0: 282 | version "2.3.0" 283 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 284 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= 285 | dependencies: 286 | ee-first "1.1.1" 287 | 288 | parseurl@~1.3.3: 289 | version "1.3.3" 290 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 291 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 292 | 293 | path-to-regexp@0.1.7: 294 | version "0.1.7" 295 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 296 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 297 | 298 | proxy-addr@~2.0.5: 299 | version "2.0.6" 300 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" 301 | integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== 302 | dependencies: 303 | forwarded "~0.1.2" 304 | ipaddr.js "1.9.1" 305 | 306 | qs@6.7.0: 307 | version "6.7.0" 308 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" 309 | integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== 310 | 311 | range-parser@~1.2.1: 312 | version "1.2.1" 313 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 314 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 315 | 316 | raw-body@2.4.0: 317 | version "2.4.0" 318 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" 319 | integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== 320 | dependencies: 321 | bytes "3.1.0" 322 | http-errors "1.7.2" 323 | iconv-lite "0.4.24" 324 | unpipe "1.0.0" 325 | 326 | regenerator-runtime@^0.13.4: 327 | version "0.13.7" 328 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" 329 | integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== 330 | 331 | safe-buffer@5.1.2: 332 | version "5.1.2" 333 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 334 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 335 | 336 | "safer-buffer@>= 2.1.2 < 3": 337 | version "2.1.2" 338 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 339 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 340 | 341 | send@0.17.1: 342 | version "0.17.1" 343 | resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" 344 | integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== 345 | dependencies: 346 | debug "2.6.9" 347 | depd "~1.1.2" 348 | destroy "~1.0.4" 349 | encodeurl "~1.0.2" 350 | escape-html "~1.0.3" 351 | etag "~1.8.1" 352 | fresh "0.5.2" 353 | http-errors "~1.7.2" 354 | mime "1.6.0" 355 | ms "2.1.1" 356 | on-finished "~2.3.0" 357 | range-parser "~1.2.1" 358 | statuses "~1.5.0" 359 | 360 | serve-static@1.14.1: 361 | version "1.14.1" 362 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" 363 | integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== 364 | dependencies: 365 | encodeurl "~1.0.2" 366 | escape-html "~1.0.3" 367 | parseurl "~1.3.3" 368 | send "0.17.1" 369 | 370 | setprototypeof@1.1.1: 371 | version "1.1.1" 372 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" 373 | integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== 374 | 375 | "statuses@>= 1.5.0 < 2", statuses@~1.5.0: 376 | version "1.5.0" 377 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 378 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 379 | 380 | toidentifier@1.0.0: 381 | version "1.0.0" 382 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" 383 | integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== 384 | 385 | type-is@~1.6.17, type-is@~1.6.18: 386 | version "1.6.18" 387 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 388 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 389 | dependencies: 390 | media-typer "0.3.0" 391 | mime-types "~2.1.24" 392 | 393 | unpipe@1.0.0, unpipe@~1.0.0: 394 | version "1.0.0" 395 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 396 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 397 | 398 | utils-merge@1.0.1: 399 | version "1.0.1" 400 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 401 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= 402 | 403 | vary@~1.1.2: 404 | version "1.1.2" 405 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 406 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 407 | --------------------------------------------------------------------------------