From fe7d386f6711ceee5cf91e438fc066b93c6202a9 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Fri, 7 Jul 2017 15:18:52 -0700 Subject: [PATCH 01/16] Add translation babel plugins and module to generate json strings file --- examples/siteConfig.js | 1 - lib/server/find-strings-plugin.js | 45 +++++++++++++++++++ lib/server/translate-plugin.js | 49 +++++++++++++++++++++ lib/server/translation.js | 2 +- lib/server/writeTranslations.js | 73 +++++++++++++++++++++++++++++++ package.json | 5 +-- 6 files changed, 169 insertions(+), 6 deletions(-) create mode 100644 lib/server/find-strings-plugin.js create mode 100644 lib/server/translate-plugin.js create mode 100644 lib/server/writeTranslations.js diff --git a/examples/siteConfig.js b/examples/siteConfig.js index d8fdaf3373..e599020bb8 100644 --- a/examples/siteConfig.js +++ b/examples/siteConfig.js @@ -79,7 +79,6 @@ const siteConfig = { let languages; if (fs.existsSync("./languages.js")) { languages = require("./languages.js"); - siteConfig["en"] = require("./i18n/en.json"); } else { languages = [ { diff --git a/lib/server/find-strings-plugin.js b/lib/server/find-strings-plugin.js new file mode 100644 index 0000000000..05b93f1c7a --- /dev/null +++ b/lib/server/find-strings-plugin.js @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* find all strings with their descriptions that need to be translated and write + to i18n/en.json file */ + +const fs = require("fs"); + +module.exports = function findStringsPlugin(babel) { + const { types: t } = babel; + + const translationsFile = process.cwd() + "/i18n/en.json"; + let currentTranslations = JSON.parse( + fs.readFileSync(translationsFile, "utf8") + ); + + return { + visitor: { + JSXElement(path) { + if (path.node.openingElement.name.name !== "Translate") { + return; + } + const text = path.node.children[0].value.trim(); + let description; + const attributes = path.node.openingElement.attributes; + for (let i = 0; i < attributes.length; i++) { + if (attributes[i].name.name === "desc") { + description = attributes[i].value.value; + } + } + if (!currentTranslations["pages-strings"]) { + currentTranslations["pages-strings"] = {}; + } + currentTranslations["pages-strings"][text + "|" + description] = text; + fs.writeFileSync(translationsFile, JSON.stringify(currentTranslations)); + } + } + }; +}; diff --git a/lib/server/translate-plugin.js b/lib/server/translate-plugin.js new file mode 100644 index 0000000000..845d079dbc --- /dev/null +++ b/lib/server/translate-plugin.js @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* replaces translate tags with calls to translate function */ + +module.exports = function translatePlugin(babel) { + const { types: t } = babel; + + return { + visitor: { + JSXElement(path) { + if (path.node.openingElement.name.name !== "translate") { + return; + } + /* assume translate element only has one child which is the text */ + const text = path.node.children[0].value.trim(); + let description; + const attributes = path.node.openingElement.attributes; + for (let i = 0; i < attributes.length; i++) { + if (attributes[i].name.name === "desc") { + description = attributes[i].value.value; + } + } + /* use an expression container if inside a jsxelement */ + if (path.findParent(path => true).node.type === "JSXElement") { + path.replaceWith( + t.jSXExpressionContainer( + t.callExpression(t.identifier("translate"), [ + t.stringLiteral(text + "|" + description) + ]) + ) + ); + } else { + path.replaceWith( + t.callExpression(t.identifier("translate"), [ + t.stringLiteral(text + "|" + description) + ]) + ); + } + } + } + }; +}; diff --git a/lib/server/translation.js b/lib/server/translation.js index 504c602828..b22c092fe4 100644 --- a/lib/server/translation.js +++ b/lib/server/translation.js @@ -73,7 +73,7 @@ function injectContent() { }); let injectedContent = ''; - languages.filter(language => language != 'en').forEach(language => { + languages.forEach(language => { injectedContent += "\nsiteConfig['" + language + diff --git a/lib/server/writeTranslations.js b/lib/server/writeTranslations.js new file mode 100644 index 0000000000..3868d0ff38 --- /dev/null +++ b/lib/server/writeTranslations.js @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* generate the i18n/en.json file */ + +const CWD = process.cwd(); +const fs = require("fs-extra"); +const mkdirp = require("mkdirp"); +const glob = require("glob"); +const readMetadata = require("./readMetadata.js"); +const path = require("path"); +const shell = require("shelljs"); +const siteConfig = require(CWD + "/siteConfig.js"); + +function writeFileAndCreateFolder(file, content) { + mkdirp.sync(file.replace(new RegExp("/[^/]*$"), "")); + fs.writeFileSync(file, content); +} + +function execute() { + let translations = { + "localized-strings": { + next: "Next", + previous: "Previous" + }, + tagline: siteConfig.tagline, + "pages-strings": {} + }; + + /* look through front matter of docs for titles and categories to translate */ + let files = glob.sync(CWD + "/../docs/en/**"); + files.forEach(file => { + const extension = path.extname(file); + if (extension === ".md" || extension === ".markdown") { + const metadata = readMetadata.extractMetadata( + fs.readFileSync(file, "utf8") + ).metadata; + + translations["localized-strings"][metadata.id] = metadata.title; + translations["localized-strings"][metadata.category] = metadata.category; + } + }); + /* look through header links for text to translate */ + for (let i = 0; i < siteConfig.headerLinksInternal.length; i++) { + translations["localized-strings"][siteConfig.headerLinksInternal[i].text] = + siteConfig.headerLinksInternal[i].text; + } + for (let i = 0; i < siteConfig.headerLinksExternal.length; i++) { + translations["localized-strings"][siteConfig.headerLinksExternal[i].text] = + siteConfig.headerLinksExternal[i].text; + } + writeFileAndCreateFolder(CWD + "/i18n/en.json", JSON.stringify(translations)); + + /* go through pages to look for text inside translate tags */ + const plugin = __dirname + "/find-strings-plugin.js"; + files = glob.sync(CWD + "/pages/en/**"); + files.forEach(file => { + const extension = path.extname(file); + if (extension === ".js") { + shell.exec( + `babel ${file} --plugins=${plugin} --presets=react > /dev/null` + ); + } + }); +} + +module.exports = execute; diff --git a/package.json b/package.json index 7d5441e459..45440ab8f2 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "examples": "./lib/copy-examples.js" }, "dependencies": { + "babel-cli": "^6.24.1", "babel-preset-react": "^6.24.1", "babel-register": "^6.24.1", "classnames": "^2.2.5", @@ -17,10 +18,6 @@ "request": "^2.81.0", "shelljs": "^0.7.8" }, - "devDependencies": { - "babel-cli": "^6.24.1", - "babel-preset-react": "^6.24.1" - }, "name": "docusaurus", "version": "1.0.0-alpha.12", "bin": { From d620f2e6dd004e092106739046585f1e95af563e Mon Sep 17 00:00:00 2001 From: Frank Li Date: Fri, 7 Jul 2017 18:15:06 -0700 Subject: [PATCH 02/16] Use babylon and babel-traverse directly to find strings to translate --- examples/siteConfig.js | 1 - lib/server/translate-plugin.js | 49 +++++++++++++++++ lib/server/translation.js | 30 +---------- lib/server/writeTranslations.js | 93 +++++++++++++++++++++++++++++++++ package.json | 6 +-- 5 files changed, 145 insertions(+), 34 deletions(-) create mode 100644 lib/server/translate-plugin.js create mode 100644 lib/server/writeTranslations.js diff --git a/examples/siteConfig.js b/examples/siteConfig.js index d8fdaf3373..e599020bb8 100644 --- a/examples/siteConfig.js +++ b/examples/siteConfig.js @@ -79,7 +79,6 @@ const siteConfig = { let languages; if (fs.existsSync("./languages.js")) { languages = require("./languages.js"); - siteConfig["en"] = require("./i18n/en.json"); } else { languages = [ { diff --git a/lib/server/translate-plugin.js b/lib/server/translate-plugin.js new file mode 100644 index 0000000000..845d079dbc --- /dev/null +++ b/lib/server/translate-plugin.js @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* replaces translate tags with calls to translate function */ + +module.exports = function translatePlugin(babel) { + const { types: t } = babel; + + return { + visitor: { + JSXElement(path) { + if (path.node.openingElement.name.name !== "translate") { + return; + } + /* assume translate element only has one child which is the text */ + const text = path.node.children[0].value.trim(); + let description; + const attributes = path.node.openingElement.attributes; + for (let i = 0; i < attributes.length; i++) { + if (attributes[i].name.name === "desc") { + description = attributes[i].value.value; + } + } + /* use an expression container if inside a jsxelement */ + if (path.findParent(path => true).node.type === "JSXElement") { + path.replaceWith( + t.jSXExpressionContainer( + t.callExpression(t.identifier("translate"), [ + t.stringLiteral(text + "|" + description) + ]) + ) + ); + } else { + path.replaceWith( + t.callExpression(t.identifier("translate"), [ + t.stringLiteral(text + "|" + description) + ]) + ); + } + } + } + }; +}; diff --git a/lib/server/translation.js b/lib/server/translation.js index 504c602828..5c9df86352 100644 --- a/lib/server/translation.js +++ b/lib/server/translation.js @@ -12,7 +12,6 @@ const fs = require('fs-extra'); const glob = require('glob'); const path = require('path'); const mkdirp = require('mkdirp'); -let siteConfig; console.log('translation.js triggered...'); @@ -24,38 +23,11 @@ function writeFileAndCreateFolder(file, content) { function execute() { if (fs.existsSync(CWD + '/languages.js')) { injectContent(); - translatePages(); } else { console.log('No languages besides English enabled'); } } -function translatePages() { - const siteConfig = require(CWD + '/siteConfig.js'); - - const files = glob.sync(CWD + '/pages/en/**/*.js'); - files.forEach(file => { - let fileContent = fs.readFileSync(file, 'utf8'); - let baseName = path.basename(file, '.js'); - - for (let i = 0; i < siteConfig['languages'].length; i++) { - let language = siteConfig['languages'][i]; - if (language.tag === 'en') continue; - if (siteConfig[language.tag]) { - let translatedContent = fileContent.slice(0); - - Object.keys(siteConfig[language.tag]["pages-strings"][baseName]) - .forEach(function(key) { - translatedContent = translatedContent.replace(key, siteConfig[language.tag]["pages-strings"][baseName][key]); - }); - - let translatedFile = file.replace('/en/', '/' + language.tag + '/'); - writeFileAndCreateFolder(translatedFile, translatedContent); - } - } - }); -} - function injectContent() { const I18N_JSON_DIR = CWD + '/i18n/'; @@ -73,7 +45,7 @@ function injectContent() { }); let injectedContent = ''; - languages.filter(language => language != 'en').forEach(language => { + languages.forEach(language => { injectedContent += "\nsiteConfig['" + language + diff --git a/lib/server/writeTranslations.js b/lib/server/writeTranslations.js new file mode 100644 index 0000000000..5f9d7b579a --- /dev/null +++ b/lib/server/writeTranslations.js @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +/* generate the i18n/en.json file */ + +const CWD = process.cwd(); +const fs = require("fs-extra"); +const mkdirp = require("mkdirp"); +const glob = require("glob"); +const readMetadata = require("./readMetadata.js"); +const path = require("path"); +const siteConfig = require(CWD + "/siteConfig.js"); +const babylon = require("babylon"); +const traverse = require("babel-traverse").default; + +function writeFileAndCreateFolder(file, content) { + mkdirp.sync(file.replace(new RegExp("/[^/]*$"), "")); + fs.writeFileSync(file, content); +} + +function execute() { + let translations = { + "localized-strings": { + next: "Next", + previous: "Previous" + }, + tagline: siteConfig.tagline, + "pages-strings": {} + }; + + /* look through front matter of docs for titles and categories to translate */ + let files = glob.sync(CWD + "/../docs/en/**"); + files.forEach(file => { + const extension = path.extname(file); + if (extension === ".md" || extension === ".markdown") { + const metadata = readMetadata.extractMetadata( + fs.readFileSync(file, "utf8") + ).metadata; + + translations["localized-strings"][metadata.id] = metadata.title; + translations["localized-strings"][metadata.category] = metadata.category; + } + }); + /* look through header links for text to translate */ + for (let i = 0; i < siteConfig.headerLinksInternal.length; i++) { + translations["localized-strings"][siteConfig.headerLinksInternal[i].text] = + siteConfig.headerLinksInternal[i].text; + } + for (let i = 0; i < siteConfig.headerLinksExternal.length; i++) { + translations["localized-strings"][siteConfig.headerLinksExternal[i].text] = + siteConfig.headerLinksExternal[i].text; + } + + /* go through pages to look for text inside translate tags */ + const plugin = __dirname + "/find-strings-plugin.js"; + files = glob.sync(CWD + "/pages/en/**"); + files.forEach(file => { + const extension = path.extname(file); + if (extension === ".js") { + const ast = babylon.parse(fs.readFileSync(file, "utf8"), { + plugins: ["jsx"] + }); + traverse(ast, { + enter(path) { + if ( + path.node.type === "JSXElement" && + path.node.openingElement.name.name === "translate" + ) { + const text = path.node.children[0].value.trim(); + let description; + const attributes = path.node.openingElement.attributes; + for (let i = 0; i < attributes.length; i++) { + if (attributes[i].name.name === "desc") { + description = attributes[i].value.value; + } + } + translations["pages-strings"][text + "|" + description] = text; + } + } + }); + } + }); + + writeFileAndCreateFolder(CWD + "/i18n/en.json", JSON.stringify(translations)); +} + +module.exports = execute; diff --git a/package.json b/package.json index 7d5441e459..67d0a8cd27 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,8 @@ "dependencies": { "babel-preset-react": "^6.24.1", "babel-register": "^6.24.1", + "babel-traverse": "^6.25.0", + "babylon": "^6.17.4", "classnames": "^2.2.5", "express": "^4.15.3", "fs-extra": "^3.0.1", @@ -17,10 +19,6 @@ "request": "^2.81.0", "shelljs": "^0.7.8" }, - "devDependencies": { - "babel-cli": "^6.24.1", - "babel-preset-react": "^6.24.1" - }, "name": "docusaurus", "version": "1.0.0-alpha.12", "bin": { From dbbfecaf6283d0e1d82eeccc2ecbe31bacf39f8d Mon Sep 17 00:00:00 2001 From: Frank Li Date: Fri, 7 Jul 2017 18:34:09 -0700 Subject: [PATCH 03/16] Remove find-strings-plugin --- lib/server/find-strings-plugin.js | 45 ------------------------------- package.json | 1 - 2 files changed, 46 deletions(-) delete mode 100644 lib/server/find-strings-plugin.js diff --git a/lib/server/find-strings-plugin.js b/lib/server/find-strings-plugin.js deleted file mode 100644 index 05b93f1c7a..0000000000 --- a/lib/server/find-strings-plugin.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -/* find all strings with their descriptions that need to be translated and write - to i18n/en.json file */ - -const fs = require("fs"); - -module.exports = function findStringsPlugin(babel) { - const { types: t } = babel; - - const translationsFile = process.cwd() + "/i18n/en.json"; - let currentTranslations = JSON.parse( - fs.readFileSync(translationsFile, "utf8") - ); - - return { - visitor: { - JSXElement(path) { - if (path.node.openingElement.name.name !== "Translate") { - return; - } - const text = path.node.children[0].value.trim(); - let description; - const attributes = path.node.openingElement.attributes; - for (let i = 0; i < attributes.length; i++) { - if (attributes[i].name.name === "desc") { - description = attributes[i].value.value; - } - } - if (!currentTranslations["pages-strings"]) { - currentTranslations["pages-strings"] = {}; - } - currentTranslations["pages-strings"][text + "|" + description] = text; - fs.writeFileSync(translationsFile, JSON.stringify(currentTranslations)); - } - } - }; -}; diff --git a/package.json b/package.json index 5e474a52c8..67d0a8cd27 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "examples": "./lib/copy-examples.js" }, "dependencies": { - "babel-cli": "^6.24.1", "babel-preset-react": "^6.24.1", "babel-register": "^6.24.1", "babel-traverse": "^6.25.0", From ed648074f25749b45d11fb85a130dcb8af440649 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 10 Jul 2017 10:53:38 -0700 Subject: [PATCH 04/16] Fix and move writeTranslations.js file to write-translations.js, move tagline property of en.json inside localized-strings --- lib/core/Site.js | 2 +- .../writeTranslations.js => write-translations.js} | 13 ++++++------- package.json | 3 ++- 3 files changed, 9 insertions(+), 9 deletions(-) rename lib/{server/writeTranslations.js => write-translations.js} (92%) diff --git a/lib/core/Site.js b/lib/core/Site.js index b5edb8b45e..e6aeace1f6 100644 --- a/lib/core/Site.js +++ b/lib/core/Site.js @@ -48,7 +48,7 @@ class Site extends React.Component { */ render() { - const tagline = this.props.config[this.props.language] ? this.props.config[this.props.language].tagline : this.props.config.tagline; + const tagline = this.props.config[this.props.language] ? this.props.config[this.props.language]["localized-strings"].tagline : this.props.config.tagline; const title = this.props.title ? this.props.title + ' · ' + this.props.config.title : this.props.config.title + ' · ' + tagline; diff --git a/lib/server/writeTranslations.js b/lib/write-translations.js similarity index 92% rename from lib/server/writeTranslations.js rename to lib/write-translations.js index a9637cfd9b..463b916b55 100644 --- a/lib/server/writeTranslations.js +++ b/lib/write-translations.js @@ -1,3 +1,5 @@ +#!/usr/bin/env node + /** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. @@ -13,12 +15,11 @@ const CWD = process.cwd(); const fs = require("fs-extra"); const mkdirp = require("mkdirp"); const glob = require("glob"); -const readMetadata = require("./readMetadata.js"); +const readMetadata = require("./server/readMetadata.js"); const path = require("path"); const siteConfig = require(CWD + "/siteConfig.js"); const babylon = require("babylon"); const traverse = require("babel-traverse").default; -const siteConfig = require(CWD + "/siteConfig.js"); function writeFileAndCreateFolder(file, content) { mkdirp.sync(file.replace(new RegExp("/[^/]*$"), "")); @@ -29,9 +30,9 @@ function execute() { let translations = { "localized-strings": { next: "Next", - previous: "Previous" + previous: "Previous", + tagline: siteConfig.tagline }, - tagline: siteConfig.tagline, "pages-strings": {} }; @@ -59,7 +60,6 @@ function execute() { } /* go through pages to look for text inside translate tags */ - const plugin = __dirname + "/find-strings-plugin.js"; files = glob.sync(CWD + "/pages/en/**"); files.forEach(file => { const extension = path.extname(file); @@ -89,7 +89,6 @@ function execute() { }); writeFileAndCreateFolder(CWD + "/i18n/en.json", JSON.stringify(translations)); - } -module.exports = execute; +execute(); diff --git a/package.json b/package.json index 67d0a8cd27..879e875516 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "docusaurus-start": "./lib/start-server.js", "docusaurus-build": "./lib/build-files.js", "docusaurus-publish": "./lib/publish-gh-pages.js", - "docusaurus-examples": "./lib/copy-examples.js" + "docusaurus-examples": "./lib/copy-examples.js", + "docusaurus-write-translations": "./lib/write-translations.js" } } From 30a8ff329a69c5a3143c39dcbd79d444ea4a5bf2 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 10 Jul 2017 14:08:00 -0700 Subject: [PATCH 05/16] Integrate handling of translation tag into static html building, add default description to translate tags --- lib/server/generate.js | 46 +++++++++++++++++++++++++--------- lib/server/translate-plugin.js | 2 +- lib/server/translate.js | 25 ++++++++++++++++++ lib/write-translations.js | 2 +- 4 files changed, 61 insertions(+), 14 deletions(-) create mode 100644 lib/server/translate.js diff --git a/lib/server/generate.js b/lib/server/generate.js index 95d0cb061e..9eed3661fc 100644 --- a/lib/server/generate.js +++ b/lib/server/generate.js @@ -22,6 +22,7 @@ const glob = require('glob'); const Site = require('../core/Site.js'); const siteConfig = require(CWD + '/siteConfig.js'); + const translate = require('./translate.js'); let languages; if (fs.existsSync(CWD + '/languages.js')) { languages = require(CWD + '/languages.js'); @@ -224,26 +225,47 @@ files = glob.sync(CWD + '/pages/**'); files.forEach(file => { if (file.match(/\.js$/)) { - let parts = file.split('pages'); - let tempFile = __dirname +'/../pages' + parts[1]; + /* make temp file for sake of require paths */ + const parts = file.split('pages'); + let tempFile = __dirname + '/../pages' + parts[1]; tempFile = tempFile.replace(path.basename(file), 'temp' + path.basename(file)); mkdirp.sync(tempFile.replace(new RegExp('/[^/]*$'), '')); fs.copySync(file, tempFile); - let language = 'en'; + const ReactComp = require(tempFile); + + let targetFile = __dirname + '/../../build/' + siteConfig.projectName + '/' + parts[1]; + targetFile = targetFile.replace(/\.js$/, '.html'); + const regexLang = /\/pages\/(.*)\//; const match = regexLang.exec(file); - let langParts = match[1].split('/'); - for (let i = 0; i < langParts.length; i++) { - if (enabledLanguages.indexOf(langParts[i]) !== -1) { - language = langParts[i]; + const langParts = match[1].split('/'); + if (langParts.indexOf('en') !== -1) { + /* copy and compile a page for each enabled language from the English file */ + for (let i = 0; i < enabledLanguages.length; i++) { + let language = enabledLanguages[i]; + /* skip conversion from english file if a file exists for this language */ + if (language !== 'en' && fs.existsSync(file.replace('/en/', '/' + language + '/'))) { + continue; + } + translate.setLanguage(language); + const str = renderToStaticMarkup(); + writeFileAndCreateFolder(targetFile.replace('/en/', '/' + language + '/'), str); } + + } + /* allow for rendering of other files not in pages/en folder */ + else { + let language = 'en'; + for (let i = 0; i < langParts.length; i++) { + if (enabledLanguages.indexOf(langParts[i]) !== -1) { + language = langParts[i]; + } + } + translate.setLanguage(language); + const str = renderToStaticMarkup(); + writeFileAndCreateFolder(targetFile, str); } - let targetFile = __dirname + '/../../build' + '/' + siteConfig.projectName + '/' + parts[1]; - targetFile = targetFile.replace(/\.js$/, '.html'); - const ReactComp = require(tempFile); - const str = renderToStaticMarkup(); - writeFileAndCreateFolder(targetFile, str); fs.removeSync(tempFile); } diff --git a/lib/server/translate-plugin.js b/lib/server/translate-plugin.js index 845d079dbc..cfadac8ca1 100644 --- a/lib/server/translate-plugin.js +++ b/lib/server/translate-plugin.js @@ -20,7 +20,7 @@ module.exports = function translatePlugin(babel) { } /* assume translate element only has one child which is the text */ const text = path.node.children[0].value.trim(); - let description; + let description = "no description given"; const attributes = path.node.openingElement.attributes; for (let i = 0; i < attributes.length; i++) { if (attributes[i].name.name === "desc") { diff --git a/lib/server/translate.js b/lib/server/translate.js new file mode 100644 index 0000000000..44dfed7602 --- /dev/null +++ b/lib/server/translate.js @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2017-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +const siteConfig = require(CWD + "/siteConfig.js"); + +let language = "en"; + +function setLanguage(lang) { + language = lang; +} + +function translate(str) { + return siteConfig[language]["pages-strings"][str]; +} + +module.exports = { + setLanguage: setLanguage, + translate: translate +}; diff --git a/lib/write-translations.js b/lib/write-translations.js index 463b916b55..7dff6ff650 100644 --- a/lib/write-translations.js +++ b/lib/write-translations.js @@ -74,7 +74,7 @@ function execute() { path.node.openingElement.name.name === "translate" ) { const text = path.node.children[0].value.trim(); - let description; + let description = "no description given"; const attributes = path.node.openingElement.attributes; for (let i = 0; i < attributes.length; i++) { if (attributes[i].name.name === "desc") { From a7b5148e06ecdc4ed622f035bab0d3d4fc321fbe Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 10 Jul 2017 16:34:07 -0700 Subject: [PATCH 06/16] Add plugin usage to build script, update example files --- examples/i18n/en.json | 59 ------------------------------------------ examples/siteConfig.js | 7 +++-- lib/build-files.js | 7 ++--- 3 files changed, 9 insertions(+), 64 deletions(-) delete mode 100644 examples/i18n/en.json diff --git a/examples/i18n/en.json b/examples/i18n/en.json deleted file mode 100644 index f7b3e9735e..0000000000 --- a/examples/i18n/en.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "localized-strings": { - "doc1": "Docusaurus", - "doc2": "The Second in a Series of Documents", - "doc3": "The Third in a Series of Documents", - "doc4": "Separate Sidebar Document 1", - "doc5": "Separate Sidebar Document 2", - "Docusaurus": "Docusaurus Guide", - "First Category": "Example Category 1", - "Second Category": "Example Category 2", - "previous": "Previous", - "next": "Continue Reading", - "Docs": "Docs", - "API": "API", - "GitHub": "GitHub", - "Help": "Help", - "Blog": "Blog" - }, - "tagline": "Tagline", - "pages-strings": { - "index": { - "My Tagline": "My Tagline", - "Try It Out": "Try It Out", - "Example Link": "Example Link", - "Example Link 2": "Example Link 2", - "This is the content of my feature": "This is the content of my feature", - "The content of my second feature": "The content of my second feature", - "These are features of this project": "These are features of this project", - "Talk about learning how to use this": "Talk about learning how to use this", - "Talk about trying this out": "Talk about trying this out", - "This is another description of how this project is useful": "This is another description of how this project is useful", - "This project is used by all these people": "This project is used by all these people", - "More \"Docusaurus\" Users": "More \"Docusaurus\" Users", - "Feature One": "Feature One", - "Feature Two": "Feature Two", - "Feature Callout": "Feature Callout", - "Learn How": "Learn How", - "Try it Out": "Try it Out", - "Description": "Description", - "Who's Using This?": "Who's Using This?" - }, - "help": { - "Learn more using the [documentation on this site.](/test-site/docs/en/doc1.html)": "Learn more using the [documentation on this site.](/test-site/docs/en/doc1.html)", - "Browse Docs": "Browse Docs", - "Ask questions about the documentation and project": "Ask questions about the documentation and project", - "Join the community": "Join the community", - "Find out what's new with this project": "Find out what's new with this project", - "Stay up to date": "Stay up to date", - "Need help?": "Need help?", - "This project is maintained by a dedicated group of people.": "This project is maintained by a dedicated group of people." - }, - "users": { - "Who's Using This?": "Who's Using This?", - "This project is used by many folks": "This project is used by many folks", - "Are you using this project?": "Are you using this project?", - "Add your company": "Add your company" - } - } -} diff --git a/examples/siteConfig.js b/examples/siteConfig.js index e599020bb8..46a3f6418b 100644 --- a/examples/siteConfig.js +++ b/examples/siteConfig.js @@ -7,8 +7,6 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -const fs = require("fs"); - /* List of projects/orgs using your project for the users page */ const users = [ { @@ -76,6 +74,11 @@ const siteConfig = { /* gaTrackingId: "" */ }; + +/* DO NOT EDIT BELOW THIS LINE */ + +const fs = require("fs"); + let languages; if (fs.existsSync("./languages.js")) { languages = require("./languages.js"); diff --git a/lib/build-files.js b/lib/build-files.js index 0b4a623595..bcc149e8b6 100644 --- a/lib/build-files.js +++ b/lib/build-files.js @@ -9,10 +9,11 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -require('babel-register') ({ +require("babel-register")({ ignore: false, - "presets": ["react"] + plugins: [require("./server/translate-plugin.js")], + presets: ["react"] }); -const generate = require('./server/generate.js'); +const generate = require("./server/generate.js"); generate(); From fbae29b0ff0dafe31c29d60ff725648838e386d2 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Mon, 10 Jul 2017 16:38:35 -0700 Subject: [PATCH 07/16] Run Prettier --- examples/siteConfig.js | 9 +- lib/copy-examples.js | 4 +- lib/core/BlogPageLayout.js | 59 +++-- lib/core/BlogPost.js | 59 +++-- lib/core/BlogPostLayout.js | 24 +- lib/core/BlogSidebar.js | 22 +- lib/core/CompLibrary.js | 8 +- lib/core/Container.js | 33 +-- lib/core/Doc.js | 18 +- lib/core/DocsLayout.js | 57 +++-- lib/core/DocsSidebar.js | 14 +- lib/core/Footer.js | 36 ++- lib/core/GridBlock.js | 55 +++-- lib/core/Head.js | 24 +- lib/core/Header.js | 16 +- lib/core/Marked.js | 460 +++++++++++++++++++---------------- lib/core/Prism.js | 308 ++++++++++++----------- lib/core/Site.js | 61 +++-- lib/core/nav/HeaderNav.js | 36 +-- lib/core/nav/SideNav.js | 47 ++-- lib/core/toSlug.js | 33 ++- lib/core/unindent.js | 8 +- lib/publish-gh-pages.js | 56 +++-- lib/server/generate.js | 308 +++++++++++++++-------- lib/server/readCategories.js | 43 ++-- lib/server/readMetadata.js | 102 ++++---- lib/server/server.js | 358 +++++++++++++++------------ lib/server/translation.js | 34 +-- lib/start-server.js | 6 +- 29 files changed, 1311 insertions(+), 987 deletions(-) diff --git a/examples/siteConfig.js b/examples/siteConfig.js index 46a3f6418b..606a023219 100644 --- a/examples/siteConfig.js +++ b/examples/siteConfig.js @@ -64,17 +64,18 @@ const siteConfig = { "rgba(46, 133, 85, 0.03)" /* primaryColor in rgba form, with 0.03 alpha */ }, tagline: "My Tagline", - recruitingLink: "https://crowdin.com/project/test-site" /* translation site "help translate" link */, + recruitingLink: + "https://crowdin.com/project/test-site" /* translation site "help translate" link */, /* remove this section to disable search bar */ algolia: { - apiKey: "0f9f28b9ab9efae89810921a351753b5", /* use your search-only api key */ + apiKey: + "0f9f28b9ab9efae89810921a351753b5" /* use your search-only api key */, indexName: "github" - }, + } /* remove this to disable google analytics tracking */ /* gaTrackingId: "" */ }; - /* DO NOT EDIT BELOW THIS LINE */ const fs = require("fs"); diff --git a/lib/copy-examples.js b/lib/copy-examples.js index ba54b12657..7bb893d1cd 100644 --- a/lib/copy-examples.js +++ b/lib/copy-examples.js @@ -10,6 +10,6 @@ */ const CWD = process.cwd(); -const fs = require('fs-extra'); +const fs = require("fs-extra"); -fs.copySync(__dirname + '/../examples/', CWD, {overwrite: false}); +fs.copySync(__dirname + "/../examples/", CWD, { overwrite: false }); diff --git a/lib/core/BlogPageLayout.js b/lib/core/BlogPageLayout.js index f8f644380f..771e4fc5ed 100644 --- a/lib/core/BlogPageLayout.js +++ b/lib/core/BlogPageLayout.js @@ -7,44 +7,53 @@ * of patent rights can be found in the PATENTS file in the same directory. */ - const BlogPost = require('./BlogPost.js'); -const BlogSidebar = require('./BlogSidebar.js'); -const Container = require('./Container.js'); -const MetadataBlog = require('./MetadataBlog.js'); -const React = require('react'); -const Site = require('./Site.js'); +const BlogPost = require("./BlogPost.js"); +const BlogSidebar = require("./BlogSidebar.js"); +const Container = require("./Container.js"); +const MetadataBlog = require("./MetadataBlog.js"); +const React = require("react"); +const Site = require("./Site.js"); const BlogPageLayout = React.createClass({ getPageURL(page) { - let url = this.props.config.baseUrl + 'blog/'; + let url = this.props.config.baseUrl + "blog/"; if (page > 0) { - url += 'page' + (page + 1) + '/'; + url += "page" + (page + 1) + "/"; } - return url + '#content'; + return url + "#content"; }, render() { const perPage = this.props.metadata.perPage; const page = this.props.metadata.page; return ( - +
- +
- {MetadataBlog - .slice(page * perPage, (page + 1) * perPage) - .map(post => { - return ( - - ); - })} + {MetadataBlog.slice( + page * perPage, + (page + 1) * perPage + ).map(post => { + return ( + + ); + })} ); - }, + } }); module.exports = BlogPageLayout; diff --git a/lib/core/BlogPost.js b/lib/core/BlogPost.js index 3f92f32024..71da9c3e3c 100644 --- a/lib/core/BlogPost.js +++ b/lib/core/BlogPost.js @@ -7,26 +7,35 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -const Marked = require('./Marked.js'); -const React = require('react'); +const Marked = require("./Marked.js"); +const React = require("react"); class BlogPost extends React.Component { renderContent() { let content = this.props.content; if (this.props.truncate) { - content = content.split('')[0]; + content = content.split("")[0]; return ( ); } - return {content}; + return ( + + {content} + + ); } renderAuthorPhoto() { @@ -37,9 +46,9 @@ class BlogPost extends React.Component { @@ -54,7 +63,9 @@ class BlogPost extends React.Component { const post = this.props.post; return (

- {post.title} + + {post.title} +

); } @@ -65,18 +76,18 @@ class BlogPost extends React.Component { // Because JavaScript sucks at date handling :( const year = match[1]; const month = [ - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December', + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December" ][parseInt(match[2], 10) - 1]; const day = parseInt(match[3], 10); @@ -84,7 +95,9 @@ class BlogPost extends React.Component {
{this.renderAuthorPhoto()}

- {post.author} + + {post.author} +

{this.renderTitle()}

diff --git a/lib/core/BlogPostLayout.js b/lib/core/BlogPostLayout.js index dab0433f20..fe713e4030 100644 --- a/lib/core/BlogPostLayout.js +++ b/lib/core/BlogPostLayout.js @@ -7,11 +7,11 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -const React = require('react'); -const BlogPost = require('./BlogPost.js'); -const BlogSidebar = require('./BlogSidebar.js'); -const Container = require('./Container.js'); -const Site = require('./Site.js'); +const React = require("react"); +const BlogPost = require("./BlogPost.js"); +const BlogSidebar = require("./BlogSidebar.js"); +const Container = require("./Container.js"); +const Site = require("./Site.js"); class BlogPostLayout extends React.Component { render() { @@ -19,20 +19,24 @@ class BlogPostLayout extends React.Component {

- +
diff --git a/lib/core/BlogSidebar.js b/lib/core/BlogSidebar.js index c4311dfba3..978a2017d1 100644 --- a/lib/core/BlogSidebar.js +++ b/lib/core/BlogSidebar.js @@ -7,22 +7,24 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -const React = require('react'); -const Container = require('./Container.js'); -const SideNav = require('./nav/SideNav.js'); +const React = require("react"); +const Container = require("./Container.js"); +const SideNav = require("./nav/SideNav.js"); -const MetadataBlog = require('./MetadataBlog.js'); +const MetadataBlog = require("./MetadataBlog.js"); class BlogSidebar extends React.Component { render() { - const contents = [{ - name: 'Recent Posts', - links: MetadataBlog, - }]; + const contents = [ + { + name: "Recent Posts", + links: MetadataBlog + } + ]; const title = this.props.current && this.props.current.title; const current = { - id: title || '', - category: 'Recent Posts', + id: title || "", + category: "Recent Posts" }; return ( diff --git a/lib/core/CompLibrary.js b/lib/core/CompLibrary.js index 1e6f3bee71..c138962b88 100644 --- a/lib/core/CompLibrary.js +++ b/lib/core/CompLibrary.js @@ -7,12 +7,12 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -const Marked = require('./Marked.js'); -const Container = require('./Container.js'); -const GridBlock = require('./GridBlock.js'); +const Marked = require("./Marked.js"); +const Container = require("./Container.js"); +const GridBlock = require("./GridBlock.js"); module.exports = { Marked: Marked, Container: Container, GridBlock: GridBlock -} +}; diff --git a/lib/core/Container.js b/lib/core/Container.js index 6dca6d8ca1..ae8573cc06 100644 --- a/lib/core/Container.js +++ b/lib/core/Container.js @@ -7,26 +7,29 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -const React = require('react'); -const classNames = require('classnames'); +const React = require("react"); +const classNames = require("classnames"); class Container extends React.Component { render() { - const containerClasses = classNames('container', this.props.className, { - 'darkBackground': this.props.background === 'dark', - 'highlightBackground': this.props.background === 'highlight', - 'lightBackground': this.props.background === 'light', - 'paddingAll': this.props.padding.indexOf('all') >= 0, - 'paddingBottom': this.props.padding.indexOf('bottom') >= 0, - 'paddingLeft': this.props.padding.indexOf('left') >= 0, - 'paddingRight': this.props.padding.indexOf('right') >= 0, - 'paddingTop': this.props.padding.indexOf('top') >= 0, + const containerClasses = classNames("container", this.props.className, { + darkBackground: this.props.background === "dark", + highlightBackground: this.props.background === "highlight", + lightBackground: this.props.background === "light", + paddingAll: this.props.padding.indexOf("all") >= 0, + paddingBottom: this.props.padding.indexOf("bottom") >= 0, + paddingLeft: this.props.padding.indexOf("left") >= 0, + paddingRight: this.props.padding.indexOf("right") >= 0, + paddingTop: this.props.padding.indexOf("top") >= 0 }); let wrappedChildren; if (this.props.wrapper) { - wrappedChildren = -
{this.props.children}
; + wrappedChildren = ( +
+ {this.props.children} +
+ ); } else { wrappedChildren = this.props.children; } @@ -39,9 +42,9 @@ class Container extends React.Component { } Container.defaultProps = { - background: 'transparent', + background: "transparent", padding: [], - wrapper: true, + wrapper: true }; module.exports = Container; diff --git a/lib/core/Doc.js b/lib/core/Doc.js index 97022dfd9c..4832a8a295 100644 --- a/lib/core/Doc.js +++ b/lib/core/Doc.js @@ -7,8 +7,8 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -const React = require('react'); -const Marked = require('./Marked.js'); +const React = require("react"); +const Marked = require("./Marked.js"); class Doc extends React.Component { render() { @@ -17,9 +17,9 @@ class Doc extends React.Component { className="edit-page-link button" href={ this.props.config.editUrl + - this.props.language + - '/' + - this.props.source + this.props.language + + "/" + + this.props.source } target="_blank" > @@ -30,10 +30,14 @@ class Doc extends React.Component {
{editLink} -

{this.props.title}

+

+ {this.props.title} +

- {this.props.content} + + {this.props.content} +
); diff --git a/lib/core/DocsLayout.js b/lib/core/DocsLayout.js index ba846a5448..6803e836de 100644 --- a/lib/core/DocsLayout.js +++ b/lib/core/DocsLayout.js @@ -7,11 +7,11 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -const React = require('react'); -const Container = require('./Container.js'); -const Doc = require('./Doc.js'); -const DocsSidebar = require('./DocsSidebar.js'); -const Site = require('./Site.js'); +const React = require("react"); +const Container = require("./Container.js"); +const Doc = require("./Doc.js"); +const DocsSidebar = require("./DocsSidebar.js"); +const Site = require("./Site.js"); class DocsLayout extends React.Component { render() { @@ -24,11 +24,13 @@ class DocsLayout extends React.Component { className="sideNavVisible" section="docs" title={ - i18n ? this.props.config[this.props.metadata.language]['localized-strings'][ - this.props.metadata.localized_id - ] || this.props.metadata.title : this.props.metadata.title + i18n + ? this.props.config[this.props.metadata.language][ + "localized-strings" + ][this.props.metadata.localized_id] || this.props.metadata.title + : this.props.metadata.title } - description={content.trim().split('\n')[0]} + description={content.trim().split("\n")[0]} language={metadata.language} >
@@ -39,9 +41,12 @@ class DocsLayout extends React.Component { config={this.props.config} source={metadata.source} title={ - i18n ? this.props.config[this.props.metadata.language]['localized-strings'][ - this.props.metadata.localized_id - ] || this.props.metadata.title : this.props.metadata.title + i18n + ? this.props.config[this.props.metadata.language][ + "localized-strings" + ][this.props.metadata.localized_id] || + this.props.metadata.title + : this.props.metadata.title } language={metadata.language} /> @@ -49,27 +54,25 @@ class DocsLayout extends React.Component { {metadata.previous_id && - ← - {' '} - { - i18n ? this.props.config[this.props.metadata.language][ - 'localized-strings' - ]['previous'] || 'Previous' : 'Previous' - } + ←{" "} + {i18n + ? this.props.config[this.props.metadata.language][ + "localized-strings" + ]["previous"] || "Previous" + : "Previous"} } {metadata.next_id && - { - i18n ? this.props.config[this.props.metadata.language][ - 'localized-strings' - ]['next'] || 'Next' : 'Next' - } - {' '} + {i18n + ? this.props.config[this.props.metadata.language][ + "localized-strings" + ]["next"] || "Next" + : "Next"}{" "} → }
diff --git a/lib/core/DocsSidebar.js b/lib/core/DocsSidebar.js index 9c6024d4b1..43883a9a9c 100644 --- a/lib/core/DocsSidebar.js +++ b/lib/core/DocsSidebar.js @@ -7,16 +7,16 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -const Metadata = require('./metadata.js'); -const React = require('react'); -const Container = require('./Container.js'); -const SideNav = require('./nav/SideNav.js'); -const siteConfig = require(process.cwd() + '/siteConfig.js'); +const Metadata = require("./metadata.js"); +const React = require("react"); +const Container = require("./Container.js"); +const SideNav = require("./nav/SideNav.js"); +const siteConfig = require(process.cwd() + "/siteConfig.js"); class DocsSidebar extends React.Component { render() { let layout = this.props.metadata.layout; - let docsCategories = require('./' + layout + 'Categories.js'); + let docsCategories = require("./" + layout + "Categories.js"); return ( + aria-label="Star this project on GitHub" + > Star ); @@ -40,21 +41,30 @@ class Footer extends React.Component {
Docs
Getting Started (or other categories) Guides (or other categories) API Reference (or other categories) @@ -62,7 +72,11 @@ class Footer extends React.Component {
More
@@ -106,6 +118,4 @@ class Footer extends React.Component { } } - - module.exports = Footer; diff --git a/lib/core/GridBlock.js b/lib/core/GridBlock.js index 30820def69..aeb6c212a7 100755 --- a/lib/core/GridBlock.js +++ b/lib/core/GridBlock.js @@ -7,31 +7,32 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -const React = require('react'); -const classNames = require('classnames'); +const React = require("react"); +const classNames = require("classnames"); -const Marked = require('./Marked.js'); +const Marked = require("./Marked.js"); class GridBlock extends React.Component { renderBlock(block) { - const blockClasses = classNames('blockElement', this.props.className, { - 'alignCenter': this.props.align === 'center', - 'alignRight': this.props.align === 'right', - 'fourByGridBlock': this.props.layout === 'fourColumn', - 'imageAlignBottom': (block.image && block.imageAlign === 'bottom'), - 'imageAlignSide': (block.image && (block.imageAlign === 'left' || - block.imageAlign === 'right')), - 'imageAlignTop': (block.image && block.imageAlign === 'top'), - 'threeByGridBlock': this.props.layout === 'threeColumn', - 'twoByGridBlock': this.props.layout === 'twoColumn', + const blockClasses = classNames("blockElement", this.props.className, { + alignCenter: this.props.align === "center", + alignRight: this.props.align === "right", + fourByGridBlock: this.props.layout === "fourColumn", + imageAlignBottom: block.image && block.imageAlign === "bottom", + imageAlignSide: + block.image && + (block.imageAlign === "left" || block.imageAlign === "right"), + imageAlignTop: block.image && block.imageAlign === "top", + threeByGridBlock: this.props.layout === "threeColumn", + twoByGridBlock: this.props.layout === "twoColumn" }); - const topLeftImage = (block.imageAlign === 'top' || - block.imageAlign === 'left') && + const topLeftImage = + (block.imageAlign === "top" || block.imageAlign === "left") && this.renderBlockImage(block.image); - const bottomRightImage = (block.imageAlign === 'bottom' || - block.imageAlign === 'right') && + const bottomRightImage = + (block.imageAlign === "bottom" || block.imageAlign === "right") && this.renderBlockImage(block.image); return ( @@ -39,7 +40,9 @@ class GridBlock extends React.Component { {topLeftImage}
{this.renderBlockTitle(block.title)} - {block.content} + + {block.content} +
{bottomRightImage}
@@ -49,7 +52,9 @@ class GridBlock extends React.Component { renderBlockImage(image) { if (image) { return ( -
+
+ +
); } else { return null; @@ -58,7 +63,11 @@ class GridBlock extends React.Component { renderBlockTitle(title) { if (title) { - return

{title}

; + return ( +

+ {title} +

+ ); } else { return null; } @@ -74,10 +83,10 @@ class GridBlock extends React.Component { } GridBlock.defaultProps = { - align: 'left', + align: "left", contents: [], - imagealign: 'top', - layout: 'twoColumn', + imagealign: "top", + layout: "twoColumn" }; module.exports = GridBlock; diff --git a/lib/core/Head.js b/lib/core/Head.js index 41c0849730..1860b83863 100644 --- a/lib/core/Head.js +++ b/lib/core/Head.js @@ -7,7 +7,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -const React = require('react'); +const React = require("react"); class Head extends React.Component { render() { @@ -29,19 +29,29 @@ class Head extends React.Component { - {this.props.title} + + {this.props.title} + - + {this.props.config.algolia && - - } - - + } + +