(i18n-js:4) use i18nliner-canvas from npm
refs FOO-2801 flag = none [change-merged][build-registry-path=jenkins/canvas-lms/foo-2801] gems/canvas_i18nliner is now a package @instructure/i18nliner-canvas and lives in the same repo on github along with the 3 other i18nliner libraries.. this was done to make it easier for maintainers to deal with this code, since changing one part may break the other due to how they're architected the source on github: https://github.com/instructure/i18nliner-js ~ test plan ~ build is still OK, this only affects the generation of files, and those i manually verified to be identical before and after Change-Id: I78afa8a808f1699c10aced8466cfade066848bc9 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/294209 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Charley Kline <ckline@instructure.com> QA-Review: Charley Kline <ckline@instructure.com> Product-Review: Charley Kline <ckline@instructure.com>
This commit is contained in:
parent
7a52d3e1ef
commit
14faf088c0
|
@ -1,6 +1,5 @@
|
|||
/doc/*
|
||||
/ui-build/**
|
||||
/gems/canvas_i18nliner/*
|
||||
/packages/*/node_modules/**
|
||||
/spec/javascripts/support/jquery.mockjax.js
|
||||
/spec/selenium/helpers/jquery.simulate.js
|
||||
|
|
2
.i18nrc
2
.i18nrc
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"plugins": [
|
||||
"@instructure/i18nliner-handlebars"
|
||||
"@instructure/i18nliner-canvas"
|
||||
],
|
||||
|
||||
"include": [
|
||||
|
|
|
@ -74,7 +74,6 @@ RUN set -eux; \
|
|||
app/stylesheets/brandable_css_brands \
|
||||
app/views/info \
|
||||
config/locales/generated \
|
||||
gems/canvas_i18nliner/node_modules \
|
||||
log \
|
||||
node_modules \
|
||||
packages/canvas-media/es \
|
||||
|
|
|
@ -42,7 +42,6 @@ RUN --mount=target=/tmp/src \
|
|||
\
|
||||
/tmp/dst && \
|
||||
find \
|
||||
gems/canvas_i18nliner \
|
||||
gems/plugins/* \
|
||||
ui/shared/* \
|
||||
packages/* \
|
||||
|
@ -69,11 +68,6 @@ RUN --mount=target=/tmp/src \
|
|||
-maxdepth 2 \
|
||||
-path "gems/*/lib" \
|
||||
-exec cp -rf --parents {} /tmp/dst \; && \
|
||||
find gems/canvas_i18nliner \
|
||||
-not -path "gems/canvas_i18nliner" \
|
||||
-not -path "gems/canvas_i18nliner/spec" \
|
||||
-not -path "gems/canvas_i18nliner/spec/*" \
|
||||
-exec cp -rf --parents {} /tmp/dst \; && \
|
||||
find gems/plugins \( \
|
||||
-path "*/app/coffeescripts" -o \
|
||||
-path "*/app/jsx" -o \
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
ember/shared/helpers/t.js
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"files": [
|
||||
{
|
||||
"pattern": "**/*.{coffee,js}",
|
||||
"processor": "js"
|
||||
},
|
||||
{
|
||||
"pattern": "ember/**/*.hbs",
|
||||
"processor": "hbs"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
/plugins
|
|
@ -18,7 +18,6 @@ services:
|
|||
- canvas-rce_canvas:/usr/src/app/packages/canvas-rce/canvas
|
||||
- canvas-rce_lib:/usr/src/app/packages/canvas-rce/lib
|
||||
- canvas-rce_node_modules:/usr/src/app/packages/canvas-rce/node_modules
|
||||
- i18nliner_node_modules:/usr/src/app/gems/canvas_i18nliner/node_modules
|
||||
- jest-moxios-utils_node_modules:/usr/src/app/packages/jest-moxios-utils/node_modules
|
||||
- js-utils_es:/usr/src/app/packages/js-utils/es
|
||||
- js-utils_lib:/usr/src/app/packages/js-utils/lib
|
||||
|
|
|
@ -28,7 +28,6 @@ services:
|
|||
- canvas-rce_canvas:/usr/src/app/packages/canvas-rce/canvas
|
||||
- canvas-rce_lib:/usr/src/app/packages/canvas-rce/lib
|
||||
- canvas-rce_node_modules:/usr/src/app/packages/canvas-rce/node_modules
|
||||
- i18nliner_node_modules:/usr/src/app/gems/canvas_i18nliner/node_modules
|
||||
- jest-moxios-utils_node_modules:/usr/src/app/packages/jest-moxios-utils/node_modules
|
||||
- js-utils_es:/usr/src/app/packages/js-utils/es
|
||||
- js-utils_lib:/usr/src/app/packages/js-utils/lib
|
||||
|
|
|
@ -9,7 +9,6 @@ services:
|
|||
- .:/usr/src/app
|
||||
- brandable_css_brands:/usr/src/app/stylesheets/brandable_css_brands
|
||||
- public_dist:/usr/src/app/public/dist
|
||||
- i18nliner_node_modules:/usr/src/app/gems/canvas_i18nliner/node_modules
|
||||
- log:/usr/src/app/log
|
||||
- node_modules:/usr/src/app/node_modules
|
||||
- tmp:/usr/src/app/tmp
|
||||
|
|
|
@ -23,7 +23,6 @@ USER docker
|
|||
RUN set -eux; \
|
||||
mkdir -p \
|
||||
app/stylesheets/brandable_css_brands \
|
||||
gems/canvas_i18nliner/node_modules \
|
||||
log \
|
||||
node_modules \
|
||||
tmp \
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
/node_modules
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"plugins": [
|
||||
"@instructure/i18nliner-handlebars"
|
||||
]
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
# canvas_i18nliner
|
||||
|
||||
## i18nliner, canvas style
|
||||
|
||||
this will replace the i18n_tasks and i18n_extraction gems
|
||||
|
||||
`.i18nrc` files must glob for files to be included through the `files` property:
|
||||
|
||||
```json
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"pattern": "**/*.js",
|
||||
"processor": "js"
|
||||
},
|
||||
{
|
||||
"pattern": "**/*.{hbs,handlebars}",
|
||||
"processor": "hbs"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`.i18nrc` files can include other directories that may in turn specify more
|
||||
files through their `.i18nrc` configuration file:
|
||||
|
||||
```json
|
||||
{
|
||||
"include": [ "relative/path/to/dir" ]
|
||||
}
|
||||
```
|
||||
|
||||
`.i18nignore` files can exclude files from processing relative to where they
|
||||
are defined (similar to `.gitignore`):
|
||||
|
||||
```json
|
||||
// file: app/.i18nignore
|
||||
foo
|
||||
bar/**/*.js
|
||||
```
|
||||
|
||||
The above ignore file will exclude `app/foo` and `app/bar/**/*.js`.
|
||||
|
||||
**Where to place ignore lists?**
|
||||
|
||||
The scanner will always look for an `.i18nignore` adjacent to `.i18nrc`, but it
|
||||
will also discover and use any `.i18nignore` file found between the root
|
||||
and the target file:
|
||||
|
||||
```
|
||||
app
|
||||
├── .i18nignore
|
||||
└── a
|
||||
├── .i18nignore
|
||||
└── b
|
||||
└── c
|
||||
```
|
||||
|
||||
A file under `app/a/b/c/` is subject to exclusion according to rules found in
|
||||
both `app/.i18nignore` and `app/a/.i18nignore`.
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../js/main').runCommand(process.argv.slice(2));
|
|
@ -1,42 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
var readline = require('readline');
|
||||
var Handlebars = require('handlebars');
|
||||
var EmberHandlebars = require('ember-template-compiler').EmberHandlebars;
|
||||
var ScopedHbsExtractor = require('../js/scoped_hbs_extractor');
|
||||
var PreProcessor = require('@instructure/i18nliner-handlebars/dist/lib/pre_processor').default;
|
||||
|
||||
// make sure necessary overrides are set up (e.g. HbsPreProcessor.normalizeInterpolationKey)
|
||||
require("../js/main");
|
||||
|
||||
var rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
terminal: false
|
||||
});
|
||||
|
||||
rl.on('line', function(line) {
|
||||
var data = JSON.parse(line);
|
||||
var path = data.path;
|
||||
var source = data.source;
|
||||
|
||||
try {
|
||||
var translationCount = 0;
|
||||
var ast = Handlebars.parse(source);
|
||||
var extractor = new ScopedHbsExtractor(ast, {path: path});
|
||||
var scope = extractor.scope;
|
||||
PreProcessor.scope = scope;
|
||||
PreProcessor.process(ast);
|
||||
extractor.forEach(function() { translationCount++; });
|
||||
|
||||
var precompiler = data.ember ? EmberHandlebars : Handlebars;
|
||||
var result = precompiler.precompile(ast).toString();
|
||||
var payload = {template: result, scope: scope, translationCount: translationCount};
|
||||
process.stdout.write(JSON.stringify(payload) + "\n");
|
||||
}
|
||||
catch (e) {
|
||||
e = e.message || e;
|
||||
process.stdout.write(JSON.stringify({error: e}) + "\n");
|
||||
}
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const { default: Errors } = require("@instructure/i18nliner/dist/lib/errors");
|
||||
|
||||
Errors.register("UnscopedTranslateCall");
|
||||
|
||||
module.exports = Errors
|
|
@ -1,80 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
var I18nliner = require("@instructure/i18nliner/dist/lib/main").default;
|
||||
var TranslateCall = require("@instructure/i18nliner/dist/lib/extractors/translate_call").default;
|
||||
var Commands = I18nliner.Commands;
|
||||
var Check = Commands.Check;
|
||||
var mkdirp = require("mkdirp");
|
||||
var fs = require("fs");
|
||||
|
||||
/*
|
||||
* GenerateJs determines what needs to go into each i18n js bundle (one
|
||||
* per "i18n!scope"), based on the I18n.t calls in the code
|
||||
*
|
||||
* outputs a json file containing a mapping of scopes <-> translation keys,
|
||||
* e.g.
|
||||
*
|
||||
* {
|
||||
* "users": [
|
||||
* "users.title",
|
||||
* "users.labels.foo",
|
||||
* "foo_bar_baz" // could be from a different scope, if called within the users scope
|
||||
* ],
|
||||
* "groups:" [
|
||||
* ...
|
||||
* ],
|
||||
* ...
|
||||
*
|
||||
*/
|
||||
|
||||
function GenerateJs(options) {
|
||||
Check.call(this, options)
|
||||
}
|
||||
|
||||
GenerateJs.prototype = Object.create(Check.prototype);
|
||||
GenerateJs.prototype.constructor = GenerateJs;
|
||||
|
||||
GenerateJs.prototype.run = function() {
|
||||
var translationsWas = TranslateCall.prototype.translations
|
||||
TranslateCall.prototype.translations = function() {
|
||||
var key = this.key;
|
||||
var defaultValue = this.defaultValue;
|
||||
|
||||
if (typeof defaultValue === 'string' || !defaultValue)
|
||||
return [[key, defaultValue]];
|
||||
|
||||
var translations = [];
|
||||
for (var k in defaultValue) {
|
||||
if (defaultValue.hasOwnProperty(k)) {
|
||||
translations.push([key + "." + k, defaultValue[k]]);
|
||||
}
|
||||
}
|
||||
return translations;
|
||||
};
|
||||
var success = Check.prototype.run.call(this);
|
||||
if (!success) return false;
|
||||
var keysByScope = this.translations.keysByScope();
|
||||
this.outputFile = './' + (this.options.outputFile || "config/locales/generated/js_bundles.json");
|
||||
mkdirp.sync(this.outputFile.replace(/\/[^\/]+$/, ''));
|
||||
fs.writeFileSync(this.outputFile, JSON.stringify(keysByScope));
|
||||
TranslateCall.prototype.translations = translationsWas
|
||||
return true;
|
||||
};
|
||||
|
||||
module.exports = GenerateJs;
|
|
@ -1,108 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
var I18nliner = require("@instructure/i18nliner/dist/lib/main").default;
|
||||
var Commands = I18nliner.Commands;
|
||||
var Check = Commands.Check;
|
||||
|
||||
var CoffeeScript = require("coffee-script");
|
||||
var babylon = require("@babel/parser");
|
||||
var fs = require('fs');
|
||||
|
||||
var AbstractProcessor = require("@instructure/i18nliner/dist/lib/processors/abstract_processor").default;
|
||||
var JsProcessor = require("@instructure/i18nliner/dist/lib/processors/js_processor").default;
|
||||
var HbsProcessor = require("@instructure/i18nliner-handlebars/dist/lib/hbs_processor").default;
|
||||
var CallHelpers = require("@instructure/i18nliner/dist/lib/call_helpers").default;
|
||||
|
||||
var scanner = require("./scanner");
|
||||
|
||||
// tell i18nliner's babylon how to handle `import('../foo').then`
|
||||
I18nliner.config.babylonPlugins.push('dynamicImport')
|
||||
I18nliner.config.babylonPlugins.push('optionalChaining')
|
||||
// tell i18nliner's babylon how to handle typescript
|
||||
I18nliner.config.babylonPlugins.push('typescript')
|
||||
|
||||
AbstractProcessor.prototype.checkFiles = function() {
|
||||
const processor = this.constructor.name.replace(/Processor/, '').toLowerCase()
|
||||
const files = scanner.getFilesForProcessor(processor)
|
||||
|
||||
for (const file of files) {
|
||||
this.checkWrapper(file, this.checkFile.bind(this))
|
||||
}
|
||||
}
|
||||
|
||||
JsProcessor.prototype.sourceFor = function(file) {
|
||||
var source = fs.readFileSync(file).toString();
|
||||
var data = { source: source, skip: !source.match(/I18n\.t/) };
|
||||
|
||||
if (!data.skip) {
|
||||
if (file.match(/\.coffee$/)) {
|
||||
data.source = CoffeeScript.compile(source, {});
|
||||
}
|
||||
data.ast = babylon.parse(data.source, { plugins: I18nliner.config.babylonPlugins, sourceType: "module" });
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
// we do the actual pre-processing in sourceFor, so just pass data straight through
|
||||
JsProcessor.prototype.preProcess = function(data) {
|
||||
return data;
|
||||
};
|
||||
|
||||
require("./scoped_hbs_pre_processor");
|
||||
var ScopedESMExtractor = require("./scoped_esm_extractor");
|
||||
var ScopedHbsExtractor = require("./scoped_hbs_extractor");
|
||||
var ScopedTranslationHash = require("./scoped_translation_hash");
|
||||
|
||||
// remove path stuff we don't want in the scope
|
||||
var pathRegex = new RegExp(
|
||||
'.*(' +
|
||||
'ui/shared/jst' +
|
||||
'|ui/features/screenreader_gradebook/jst' +
|
||||
'|packages/[^/]+/src/jst' +
|
||||
'|gems/plugins/[^/]+/app/views/jst' +
|
||||
')'
|
||||
)
|
||||
|
||||
ScopedHbsExtractor.prototype.normalizePath = function(path) {
|
||||
return path.replace(pathRegex, "").replace(/^([^\/]+\/)templates\//, '$1');
|
||||
};
|
||||
|
||||
var GenerateJs = require("./generate_js");
|
||||
Commands.Generate_js = GenerateJs;
|
||||
|
||||
// swap out the defaults for our scope-aware varieties
|
||||
Check.prototype.TranslationHash = ScopedTranslationHash;
|
||||
JsProcessor.prototype.I18nJsExtractor = ScopedESMExtractor;
|
||||
HbsProcessor.prototype.Extractor = ScopedHbsExtractor;
|
||||
CallHelpers.keyPattern = /^\#?\w+(\.\w+)+$/ // handle our absolute keys
|
||||
|
||||
module.exports = {
|
||||
I18nliner,
|
||||
runCommand: function(argv) {
|
||||
argv = require('minimist')(argv);
|
||||
|
||||
scanner.scanFilesFromI18nrc(
|
||||
scanner.loadConfigFromDirectory(
|
||||
require('path').resolve(__dirname, '../../..')
|
||||
)
|
||||
)
|
||||
|
||||
Commands.run(argv._[0], argv) || (process.exitCode = 1);
|
||||
}
|
||||
};
|
|
@ -1,81 +0,0 @@
|
|||
const path = require('path')
|
||||
const glob = require('glob')
|
||||
const fs = require('fs')
|
||||
|
||||
const filesByProcessor = { js: [], hbs: [], ts: [] }
|
||||
|
||||
const loadIgnoreFile = file => (
|
||||
fs.readFileSync(file, 'utf8')
|
||||
.trim()
|
||||
.split(/\r?\n|\r/)
|
||||
.filter(x => x.length > 0)
|
||||
.map(pattern => path.normalize(`${path.dirname(file)}/${pattern}`))
|
||||
);
|
||||
|
||||
const combineIgnores = ignore => next => ({ ...next, ignore: ignore.concat(next.ignore) })
|
||||
const discoverIgnores = files => {
|
||||
const ignoreLists = []
|
||||
const ignoreFiles = {}
|
||||
|
||||
for (const file of files) {
|
||||
const dir = path.dirname(file)
|
||||
|
||||
if (!ignoreFiles[dir]) {
|
||||
const ignoreFile = path.join(dir, '.i18nignore')
|
||||
|
||||
ignoreFiles[dir] = true
|
||||
|
||||
if (fs.existsSync(ignoreFile)) {
|
||||
ignoreLists.push(loadIgnoreFile(ignoreFile))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ignoreLists.flat()
|
||||
}
|
||||
|
||||
const loadConfigFromDirectory = dir => {
|
||||
const configFile = path.resolve(dir, '.i18nrc')
|
||||
const ignoreFile = path.resolve(dir, '.i18nignore')
|
||||
const config = fs.existsSync(configFile) ?
|
||||
JSON.parse(fs.readFileSync(configFile, 'utf8')) :
|
||||
{}
|
||||
;
|
||||
|
||||
config.cwd = dir
|
||||
config.ignore = fs.existsSync(ignoreFile) ? loadIgnoreFile(ignoreFile) : []
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
const scanFilesFromI18nrc = ({ cwd, files = [], ignore = [], include = [] }) => {
|
||||
const globopts = { cwd, absolute: true }
|
||||
|
||||
for (const { pattern, processor } of files) {
|
||||
// need 2 passes to discover .i18nignore files
|
||||
const included = glob.sync(pattern, { ignore, ...globopts })
|
||||
|
||||
filesByProcessor[processor] = filesByProcessor[processor].concat(
|
||||
glob.sync(pattern, { ignore: ignore.concat(discoverIgnores(included)), ...globopts })
|
||||
)
|
||||
}
|
||||
|
||||
for (const dir of include) {
|
||||
scanFilesFromI18nrc(
|
||||
combineIgnores(ignore)(
|
||||
loadConfigFromDirectory(
|
||||
path.resolve(cwd, dir)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
exports.getFilesForProcessor = name => filesByProcessor[name]
|
||||
exports.loadConfigFromDirectory = loadConfigFromDirectory
|
||||
exports.scanFilesFromI18nrc = scanFilesFromI18nrc
|
||||
exports.reset = () => {
|
||||
for (const processor of Object.keys(filesByProcessor)) {
|
||||
filesByProcessor[processor].splice(0)
|
||||
}
|
||||
}
|
|
@ -1,176 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const createScopedTranslateCall = require("./scoped_translate_call")
|
||||
const Errors = require("./errors");
|
||||
const { default: I18nJsExtractor } = require("@instructure/i18nliner/dist/lib/extractors/i18n_js_extractor");
|
||||
const { default: TranslateCall } = require("@instructure/i18nliner/dist/lib/extractors/translate_call");
|
||||
|
||||
const ScopedTranslateCall = createScopedTranslateCall(TranslateCall);
|
||||
const CANVAS_I18N_PACKAGE = '@canvas/i18n'
|
||||
const CANVAS_I18N_USE_SCOPE_SPECIFIER = 'useScope'
|
||||
const CANVAS_I18N_RECEIVER = 'I18n'
|
||||
|
||||
// This extractor implementation is suitable for ES modules where a module
|
||||
// imports the "useScope" function from the @canvas/i18n package and assigns the
|
||||
// output of a call to that function to a receiver named exactly "I18n". Calls
|
||||
// to the "t" or "translate" methods on that receiver will use the scope
|
||||
// supplied to that "useScope" call.
|
||||
//
|
||||
// import { useScope } from '@canvas/i18n'
|
||||
//
|
||||
// const I18n = useScope('foo')
|
||||
//
|
||||
// I18n.t('my_key', 'Hello world!')
|
||||
// // => { "foo": { "my_key": "Hello World" } }
|
||||
//
|
||||
// The extractor looks for the I18n receiver defined in the current lexical
|
||||
// scope of the call to I18n.t():
|
||||
//
|
||||
// function a() {
|
||||
// const I18n = useScope('foo')
|
||||
// I18n.t('my_key', 'Key in foo') // => foo.my_key
|
||||
// }
|
||||
//
|
||||
// function b() {
|
||||
// const I18n = useScope('bar')
|
||||
// I18n.t('my_key', 'Key in bar') // => bar.my_key
|
||||
// }
|
||||
//
|
||||
// Note that the receiver MUST be identified as "I18n". The (base) extractor
|
||||
// will fail to recognize any translate calls if the output of useScope is
|
||||
// assigned to a receiver with a different identifier. With that said, the
|
||||
// identifier for useScope can be renamed at will:
|
||||
//
|
||||
// // this is OK:
|
||||
// import { useScope as useI18nScope } from '@canvas/i18n'
|
||||
// const I18n = useI18nScope('foo')
|
||||
//
|
||||
// // this is NOT ok:
|
||||
// import { useScope } from '@canvas/i18n'
|
||||
// const smth = useScope('foo')
|
||||
//
|
||||
class ScopedESMExtractor extends I18nJsExtractor {
|
||||
constructor() {
|
||||
super(...arguments)
|
||||
|
||||
// the identifier for the "useScope" specifier imported from @canvas/i18n,
|
||||
// which may be renamed
|
||||
this.useScopeIdentifier = null
|
||||
|
||||
// mapping of "I18n" receivers to the (i18n) scopes they were assigned in
|
||||
// the call to useScope
|
||||
this.receiverScopeMapping = new WeakMap()
|
||||
};
|
||||
|
||||
enter(path) {
|
||||
// import { useScope } from '@canvas/i18n'
|
||||
// ^^^^^^^^
|
||||
// import { useScope as blah } from '@canvas/i18n'
|
||||
// ^^^^
|
||||
if (!this.useScopeIdentifier && path.type === 'ImportDeclaration') {
|
||||
trackUseScopeIdentifier.call(this, path);
|
||||
}
|
||||
// let I18n
|
||||
// ^^^^
|
||||
// I18n = useScope('foo')
|
||||
// ^^^
|
||||
// (this happens in CoffeeScript when compiled to JS)
|
||||
else if (this.useScopeIdentifier && path.type === 'AssignmentExpression') {
|
||||
indexScopeFromAssignment.call(this, path)
|
||||
}
|
||||
// const I18n = useScope('foo')
|
||||
// ^^^^ ^^^
|
||||
else if (this.useScopeIdentifier && path.type === 'VariableDeclarator') {
|
||||
indexScopeFromDeclaration.call(this, path)
|
||||
}
|
||||
|
||||
return super.enter(...arguments)
|
||||
};
|
||||
|
||||
buildTranslateCall(line, method, args, path) {
|
||||
const binding = path.scope.getBinding(CANVAS_I18N_RECEIVER)
|
||||
const scope = this.receiverScopeMapping.get(binding)
|
||||
|
||||
if (scope) {
|
||||
return new ScopedTranslateCall(line, method, args, scope);
|
||||
}
|
||||
else {
|
||||
throw new Errors.UnscopedTranslateCall(line)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
function trackUseScopeIdentifier({ node }) {
|
||||
if (
|
||||
node.source &&
|
||||
node.source.type === 'StringLiteral' &&
|
||||
node.source.value === CANVAS_I18N_PACKAGE
|
||||
) {
|
||||
const specifier = node.specifiers.find(x =>
|
||||
x.type === 'ImportSpecifier'&&
|
||||
x.imported &&
|
||||
x.imported.type === 'Identifier' &&
|
||||
x.imported.name === CANVAS_I18N_USE_SCOPE_SPECIFIER
|
||||
)
|
||||
|
||||
if (
|
||||
specifier &&
|
||||
specifier.type === 'ImportSpecifier' &&
|
||||
specifier.local &&
|
||||
specifier.local.type === 'Identifier' &&
|
||||
specifier.local.name
|
||||
) {
|
||||
this.useScopeIdentifier = specifier.local.name
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function indexScopeFromAssignment(path) {
|
||||
return indexScope.call(this, path, path.node.left, path.node.right)
|
||||
};
|
||||
|
||||
function indexScopeFromDeclaration(path) {
|
||||
return indexScope.call(this, path, path.node.id, path.node.init)
|
||||
};
|
||||
|
||||
// left: Identifier
|
||||
// right: CallExpression
|
||||
function indexScope(path, left, right) {
|
||||
if (
|
||||
left &&
|
||||
left.type === 'Identifier' &&
|
||||
left.name === CANVAS_I18N_RECEIVER &&
|
||||
right &&
|
||||
right.type === 'CallExpression' &&
|
||||
right.callee &&
|
||||
right.callee.type === 'Identifier' &&
|
||||
right.callee.name === this.useScopeIdentifier &&
|
||||
right.arguments &&
|
||||
right.arguments.length === 1 &&
|
||||
right.arguments[0].type === 'StringLiteral' &&
|
||||
right.arguments[0].value
|
||||
) {
|
||||
this.receiverScopeMapping.set(
|
||||
path.scope.getBinding(CANVAS_I18N_RECEIVER),
|
||||
right.arguments[0].value
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ScopedESMExtractor;
|
|
@ -1,78 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
var HbsExtractor = require("@instructure/i18nliner-handlebars/dist/lib/extractor").default;
|
||||
|
||||
var HbsTranslateCall = require("@instructure/i18nliner-handlebars/dist/lib/t_call").default;
|
||||
var ScopedHbsTranslateCall = require("./scoped_translate_call")(HbsTranslateCall);
|
||||
var path = require('path')
|
||||
var fs = require('fs')
|
||||
|
||||
function ScopedHbsExtractor(ast, options) {
|
||||
// read the scope from the i18nScope property in the accompanying .json file:
|
||||
this.scope = ScopedHbsExtractor.readI18nScopeFromJSONFile(
|
||||
// resolve relative to process.cwd() in case it's not absolute
|
||||
path.resolve(options.path)
|
||||
)
|
||||
|
||||
this.path = options.path // need this for error reporting
|
||||
|
||||
HbsExtractor.apply(this, arguments);
|
||||
};
|
||||
|
||||
ScopedHbsExtractor.prototype = Object.create(HbsExtractor.prototype);
|
||||
ScopedHbsExtractor.prototype.constructor = ScopedHbsExtractor;
|
||||
|
||||
ScopedHbsExtractor.prototype.normalizePath = function(path) {
|
||||
return path;
|
||||
};
|
||||
|
||||
ScopedHbsExtractor.prototype.buildTranslateCall = function(sexpr) {
|
||||
if (!this.scope) {
|
||||
const friendlyFile = path.relative(process.cwd(), this.path)
|
||||
|
||||
throw new Error(`
|
||||
canvas_i18nliner: expected i18nScope for Handlebars template to be specified in
|
||||
the accompanying .json file, but found none:
|
||||
|
||||
${friendlyFile}
|
||||
|
||||
To fix this, create the following JSON file with the "i18nScope" property set to
|
||||
the i18n scope to use for the template (e.g. similar to what you'd do in
|
||||
JavaScript, like \`import I18n from "i18n!foo.bar"\`):
|
||||
^^^^^^^
|
||||
|
||||
// file: ${friendlyFile + '.json'}
|
||||
{
|
||||
"i18nScope": "..."
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
||||
return new ScopedHbsTranslateCall(sexpr, this.scope);
|
||||
};
|
||||
|
||||
ScopedHbsExtractor.readI18nScopeFromJSONFile = function(filepath) {
|
||||
const metadataFilepath = `${filepath}.json`
|
||||
|
||||
if (fs.existsSync(metadataFilepath)) {
|
||||
return require(metadataFilepath).i18nScope
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ScopedHbsExtractor;
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
var I18nlinerHbs = require("@instructure/i18nliner-handlebars").default;
|
||||
var PreProcessor = require("@instructure/i18nliner-handlebars/dist/lib/pre_processor").default;
|
||||
var Handlebars = require("handlebars");
|
||||
var AST = Handlebars.AST;
|
||||
var StringNode = AST.StringNode;
|
||||
var HashNode = AST.HashNode;
|
||||
|
||||
// slightly more lax interpolation key format for hbs to support any
|
||||
// existing translations (camel case and dot syntax, e.g. "foo.bar.baz")
|
||||
PreProcessor.normalizeInterpolationKey = function(key) {
|
||||
key = key.replace(/[^a-z0-9.]/gi, ' ');
|
||||
key = key.trim();
|
||||
key = key.replace(/ +/g, '_');
|
||||
return key.substring(0, 32);
|
||||
};
|
||||
|
||||
// add explicit scope to all t calls (post block -> inline transformation)
|
||||
var _processStatement = PreProcessor.processStatement;
|
||||
PreProcessor.processStatement = function(statement) {
|
||||
statement = _processStatement.call(this, statement) || statement;
|
||||
if (statement.type === 'mustache' && statement.id.string === 't')
|
||||
return this.injectScope(statement);
|
||||
}
|
||||
|
||||
PreProcessor.injectScope = function(node) {
|
||||
var pairs;
|
||||
if (!node.hash)
|
||||
node.hash = node.sexpr.hash = new HashNode([]);
|
||||
pairs = node.hash.pairs;
|
||||
// to match our .rb scoping behavior, don't scope inferred keys...
|
||||
// if inferred, it's always the last option
|
||||
if (!pairs.length || pairs[pairs.length - 1][0] !== "i18n_inferred_key") {
|
||||
node.hash.pairs = pairs.concat([["scope", new StringNode(this.scope)]]);
|
||||
}
|
||||
return node;
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
module.exports = function(TranslateCall) {
|
||||
var ScopedTranslateCall = function() {
|
||||
var args = [].slice.call(arguments);
|
||||
this.scope = args.pop();
|
||||
|
||||
TranslateCall.apply(this, arguments);
|
||||
}
|
||||
|
||||
ScopedTranslateCall.prototype = Object.create(TranslateCall.prototype);
|
||||
ScopedTranslateCall.prototype.constructor = ScopedTranslateCall;
|
||||
|
||||
ScopedTranslateCall.prototype.normalizeKey = function(key) {
|
||||
if (key[0] === '#')
|
||||
return key.slice(1);
|
||||
else
|
||||
return this.scope + "." + key;
|
||||
};
|
||||
|
||||
ScopedTranslateCall.prototype.normalize = function() {
|
||||
// TODO: make i18nliner-js use the latter, just like i18nliner(.rb) ...
|
||||
// i18nliner-handlebars can't use the former
|
||||
if (!this.inferredKey && !this.options.i18n_inferred_key)
|
||||
this.key = this.normalizeKey(this.key);
|
||||
TranslateCall.prototype.normalize.call(this);
|
||||
};
|
||||
|
||||
return ScopedTranslateCall;
|
||||
}
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
var TranslationHash = require("@instructure/i18nliner/dist/lib/extractors/translation_hash").default;
|
||||
|
||||
function keys(obj) {
|
||||
var result = [];
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) result.push(key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Flatten a deeply nested object, joining intermediate keys with "."
|
||||
*
|
||||
* e.g.
|
||||
*
|
||||
* flatten({a: 1, b: {c: 2, d: {e: 3}}})
|
||||
* => {"a": 1", "b.c": 2, "b.d.e": 3}
|
||||
*/
|
||||
function flatten(obj, prefix, result) {
|
||||
result = result || {};
|
||||
var subPrefix;
|
||||
var value;
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
var value = obj[key];
|
||||
fullKey = prefix ? prefix + "." + key : key;
|
||||
if (value instanceof Object && !(value instanceof Array)) {
|
||||
flatten(value, fullKey, result);
|
||||
} else {
|
||||
result[fullKey] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/* Track a different TranslationHash for each scope.
|
||||
*
|
||||
* This is needed for i18n:generate_js, since it builds a separate
|
||||
* translation bundle for each i18n scope (so the "i18n!scope" magic
|
||||
* works)
|
||||
*/
|
||||
|
||||
function ScopedTranslationHash() {
|
||||
this.hashes = {};
|
||||
this.masterHash = new TranslationHash()
|
||||
this.translations = this.masterHash.translations;
|
||||
}
|
||||
|
||||
ScopedTranslationHash.prototype.set = function(key, value, meta) {
|
||||
this.masterHash.set(key, value, meta); // we need this for collision checking
|
||||
var scope = meta.scope;
|
||||
var hash = this.hashes[scope] = this.hashes[scope] || new TranslationHash();
|
||||
hash.set(key, value, meta);
|
||||
};
|
||||
|
||||
ScopedTranslationHash.prototype.keysByScope = function() {
|
||||
var hash = {};
|
||||
for (key in this.hashes) {
|
||||
hash[key] = keys(flatten(this.hashes[key].translations));
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
|
||||
module.exports = ScopedTranslationHash;
|
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"name": "i18nliner-canvas",
|
||||
"description": "i18nliner, canvas style",
|
||||
"private": true,
|
||||
"main": "./js/main",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@instructure/i18nliner-handlebars": "^1.0.3",
|
||||
"@instructure/i18nliner": "^2.1.0",
|
||||
"ember-template-compiler": "1.8.0",
|
||||
"glob": "^7.0.3",
|
||||
"handlebars": "1.3.0",
|
||||
"minimist": "^1.1.0",
|
||||
"mkdirp": "^0.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"coffee-script": "^1.12.4",
|
||||
"jest": "^26"
|
||||
},
|
||||
"peerDependencies": {},
|
||||
"scripts": {
|
||||
"test": "jest"
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"files": [
|
||||
{
|
||||
"pattern": "**/*.hbs",
|
||||
"processor": "hbs"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
<div>{{t 'foo' 'lulz'}}</div>
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"files": [
|
||||
{
|
||||
"pattern": "**/*.hbs",
|
||||
"processor": "hbs"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
<p>{{#t "#absolute_key"}}Absolute key{{/t}}</p>
|
||||
<p>{{#t "relative_key"}}Relative key{{/t}}</p>
|
||||
<p>{{#t}}Inferred key{{/t}}</p>
|
||||
|
||||
<p>{{t "#inline_with_absolute_key" "Inline with absolute key"}}</p>
|
||||
<p>{{t "inline_with_relative_key" "Inline with relative key"}}</p>
|
||||
<p>{{t "Inline with inferred key"}}</p>
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"i18nScope": "foo.bar_baz"
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
<p>{{t "inline_with_relative_key" "Inline with relative key"}}</p>
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"i18nScope": "foo.bar_fizz_buzz"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"files": [
|
||||
{
|
||||
"pattern": "**/*.{coffee,js,ts,tsx}",
|
||||
"processor": "js"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
Baz*
|
|
@ -1,21 +0,0 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { useScope } from '@canvas/i18n'
|
||||
|
||||
I18n = useScope('coffee')
|
||||
I18n.t("yay coffee")
|
|
@ -1,21 +0,0 @@
|
|||
#
|
||||
# Copyright (C) 2017 - present Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { useScope } from '@canvas/i18n'
|
||||
|
||||
I18n = useScope('coffee')
|
||||
I18n.t("yay plzdont")
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useScope } from '@canvas/i18n'
|
||||
|
||||
const I18n = useScope('esm')
|
||||
|
||||
I18n.t('my_key', 'Hello world')
|
||||
I18n.t("#absolute_key", "Absolute key");
|
||||
I18n.t("Inferred key");
|
||||
I18n.t("nested.relative_key", "Relative key in nested scope");
|
||||
|
||||
function a() {
|
||||
const I18n = useScope('foo')
|
||||
I18n.t("relative_key", "Relative key");
|
||||
}
|
||||
|
||||
function b() {
|
||||
const I18n = useScope('bar')
|
||||
I18n.t("relative_key", "Another relative key");
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useScope } from '@canvas/i18n'
|
||||
|
||||
let test: string = 'test'
|
||||
|
||||
const I18n = useScope('ts')
|
||||
I18n.t('yay typescript')
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const mkdirp = require("mkdirp");
|
||||
const { I18nliner } = require("../js/main");
|
||||
const scanner = require("../js/scanner");
|
||||
|
||||
class PanickyCheck extends I18nliner.Commands.Check {
|
||||
// don't print to TTY
|
||||
print() {};
|
||||
|
||||
// and do throw errors
|
||||
checkWrapper(file, checker) {
|
||||
try {
|
||||
checker(file)
|
||||
}
|
||||
catch (e) {
|
||||
throw new Error(e.message)
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var subject = function(dir) {
|
||||
var command = new PanickyCheck({});
|
||||
scanner.scanFilesFromI18nrc(scanner.loadConfigFromDirectory(dir))
|
||||
command.run();
|
||||
return command.translations.masterHash.translations;
|
||||
}
|
||||
|
||||
describe("I18nliner", function() {
|
||||
afterEach(function() {
|
||||
scanner.reset()
|
||||
})
|
||||
|
||||
describe("handlebars", function() {
|
||||
it("extracts default translations", function() {
|
||||
expect(subject("spec/fixtures/hbs")).toEqual({
|
||||
absolute_key: "Absolute key",
|
||||
inferred_key_c49e3743: "Inferred key",
|
||||
inline_with_absolute_key: "Inline with absolute key",
|
||||
inline_with_inferred_key_88e68761: "Inline with inferred key",
|
||||
foo: {
|
||||
bar_baz: {
|
||||
inline_with_relative_key: "Inline with relative key",
|
||||
relative_key: "Relative key"
|
||||
},
|
||||
bar_fizz_buzz: {
|
||||
inline_with_relative_key: "Inline with relative key"
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if no scope was specified', () => {
|
||||
const command = new PanickyCheck({});
|
||||
|
||||
scanner.scanFilesFromI18nrc(
|
||||
scanner.loadConfigFromDirectory('spec/fixtures/hbs-missing-i18n-scope')
|
||||
)
|
||||
|
||||
expect(() => {
|
||||
command.checkFiles()
|
||||
}).toThrowError(/expected i18nScope for Handlebars template to be specified/)
|
||||
})
|
||||
});
|
||||
|
||||
describe("javascript", function() {
|
||||
it("extracts default translations", function() {
|
||||
expect(subject("spec/fixtures/js")).toEqual({
|
||||
absolute_key: "Absolute key",
|
||||
inferred_key_c49e3743: "Inferred key",
|
||||
esm: {
|
||||
my_key: 'Hello world',
|
||||
nested: {
|
||||
relative_key: "Relative key in nested scope"
|
||||
},
|
||||
},
|
||||
foo: {
|
||||
relative_key: "Relative key"
|
||||
},
|
||||
bar: {
|
||||
relative_key: "Another relative key"
|
||||
},
|
||||
yay_coffee_d4d65736: 'yay coffee',
|
||||
yay_typescript_2a26bb91: 'yay typescript'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,150 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const JsProcessor = require('@instructure/i18nliner/dist/lib/processors/js_processor')['default'];
|
||||
const ScopedESMExtractor = require('../js/scoped_esm_extractor');
|
||||
const dedent = require('dedent')
|
||||
|
||||
describe('ScopedESMExtractor', () => {
|
||||
it('tracks scope through the call to @canvas/i18n#useScope', () => {
|
||||
expect(extract(dedent`
|
||||
import { useScope } from '@canvas/i18n'
|
||||
const I18n = useScope('foo')
|
||||
I18n.t('keyed', 'something')
|
||||
`).translations.translations).toEqual({
|
||||
foo: {
|
||||
keyed: 'something'
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it('tracks scope through the call to a renamed @canvas/i18n#useScope specifier', () => {
|
||||
expect(extract(dedent`
|
||||
import { useScope as useI18nScope } from '@canvas/i18n'
|
||||
const I18n = useI18nScope('foo')
|
||||
I18n.t('keyed', 'something')
|
||||
`).translations.translations).toEqual({
|
||||
foo: {
|
||||
keyed: 'something'
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it('tracks scope through assignment', () => {
|
||||
expect(extract(dedent`
|
||||
import { useScope } from '@canvas/i18n'
|
||||
|
||||
let I18n
|
||||
|
||||
I18n = useScope('foo')
|
||||
I18n.t('keyed', 'something')
|
||||
`).translations.translations).toEqual({
|
||||
foo: {
|
||||
keyed: 'something'
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it('resolves (i18n) scopes across different lexical scopes', () => {
|
||||
const { translations: translationHash } = extract(dedent`
|
||||
import { useScope } from '@canvas/i18n'
|
||||
|
||||
function a() {
|
||||
const I18n = useScope('foo')
|
||||
|
||||
I18n.t('key', 'hello')
|
||||
I18n.t('inferred')
|
||||
}
|
||||
|
||||
function b() {
|
||||
let I18n
|
||||
|
||||
I18n = useScope('bar')
|
||||
I18n.t('key', 'world')
|
||||
I18n.t('inferred')
|
||||
}
|
||||
`)
|
||||
|
||||
expect(translationHash.translations).toEqual({
|
||||
foo: { key: 'hello' },
|
||||
bar: { key: 'world' },
|
||||
inferred_7cf5962e: 'inferred'
|
||||
})
|
||||
})
|
||||
|
||||
it('extracts translations', () => {
|
||||
expect(extract(dedent`
|
||||
import { useScope } from '@canvas/i18n'
|
||||
|
||||
const I18n = useScope('foo')
|
||||
|
||||
I18n.t('#absolute', 'Unscoped')
|
||||
I18n.t('inferred')
|
||||
I18n.t('keyed', 'Keyed')
|
||||
I18n.t('nested.keyed', 'Nested')
|
||||
`).translations.translations).toEqual({
|
||||
absolute: 'Unscoped',
|
||||
foo: {
|
||||
keyed: 'Keyed',
|
||||
nested: {
|
||||
keyed: 'Nested'
|
||||
}
|
||||
},
|
||||
inferred_7cf5962e: 'inferred'
|
||||
});
|
||||
})
|
||||
|
||||
it('throws if no scope was defined by the time t() was called', () => {
|
||||
expect(() => {
|
||||
extract(dedent`
|
||||
import I18n from '@canvas/i18n'
|
||||
I18n.t('hello')
|
||||
`)
|
||||
}).toThrow(/unscoped translate call/);
|
||||
})
|
||||
|
||||
it('throws if no scope was defined using the "useScope" specifier', () => {
|
||||
expect(() => {
|
||||
extract(dedent`
|
||||
import { useScope } from '@canvas/i18n'
|
||||
const I18n = somethingElse('foo')
|
||||
I18n.t('hello')
|
||||
`)
|
||||
}).toThrow(/unscoped translate call/);
|
||||
})
|
||||
|
||||
it('throws if no scope was defined using the "useScope" interface from @canvas/i18n', () => {
|
||||
expect(() => {
|
||||
extract(dedent`
|
||||
function useScope() {}
|
||||
const I18n = useScope('foo')
|
||||
I18n.t('hello')
|
||||
`)
|
||||
}).toThrow(/unscoped translate call/);
|
||||
})
|
||||
});
|
||||
|
||||
function extract(source) {
|
||||
const extractor = new ScopedESMExtractor({
|
||||
ast: JsProcessor.prototype.parse(source)
|
||||
})
|
||||
|
||||
extractor.run()
|
||||
|
||||
return extractor;
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2021 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const HbsProcessor = require('@instructure/i18nliner-handlebars/dist/lib/hbs_processor')['default'];
|
||||
const ScopedHbsExtractor = require('../js/scoped_hbs_extractor');
|
||||
const Handlebars = require('handlebars')
|
||||
const path = require('path')
|
||||
|
||||
describe('ScopedHbsExtractor.readI18nScopeFromJSONFile', () => {
|
||||
it('reads the i18nScope from the accompanying .json file', () => {
|
||||
expect(
|
||||
ScopedHbsExtractor.readI18nScopeFromJSONFile(
|
||||
path.resolve(__dirname, 'fixtures/hbs/app/views/jst/foo/_barBaz.hbs')
|
||||
)
|
||||
).toEqual('foo.bar_baz')
|
||||
})
|
||||
});
|
|
@ -1,5 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
yarn install || yarn install --network-concurrency 1
|
||||
yarn test
|
|
@ -25,13 +25,19 @@ namespace :i18n do
|
|||
# @instructure/i18nliner on the frontend.
|
||||
source_translations_file = Rails.root.join("config/locales/generated/en.yml").to_s
|
||||
|
||||
js_i18nliner_path = Rails.root.join("gems/canvas_i18nliner/bin/i18nliner").to_s
|
||||
js_i18nliner_path = Rails.root.join("node_modules/@instructure/i18nliner-canvas/bin/i18nliner").to_s
|
||||
|
||||
# Translations extracted from the frontend source code.
|
||||
#
|
||||
# This file has a hierarchical structure, unlike the "index" one. It looks
|
||||
# similar to what the Ruby I18nliner exports.
|
||||
js_translations_file = Rails.root.join("config/locales/generated/en.json").to_s
|
||||
js_translations_file = Rails.root.join("config/locales/generated/en-js.json").to_s
|
||||
|
||||
# Input to the routine that generates JS modules for every locale, that are
|
||||
# then loaded at runtime by the frontend.
|
||||
#
|
||||
# See @instructure/i18nliner-canvas for the structure of this file.
|
||||
js_index_file = Rails.root.join("config/locales/generated/en-js-index.json").to_s
|
||||
|
||||
# Directory to contain the auto-generated translation files for the frontend.
|
||||
js_translation_modules_dir = Rails.root.join("public/javascripts/translations").to_s
|
||||
|
@ -101,7 +107,8 @@ namespace :i18n do
|
|||
task extract_js: [] do
|
||||
exit 1 unless system(
|
||||
js_i18nliner_path, "export",
|
||||
"--translationsFile", js_translations_file
|
||||
"--translationsFile", js_translations_file,
|
||||
"--indexFile", js_index_file
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -111,7 +118,7 @@ namespace :i18n do
|
|||
task generate: [:extract]
|
||||
|
||||
desc "Generates JS bundle i18n files (non-en) and adds them to assets.yml"
|
||||
task generate_js: :i18n_environment do
|
||||
task generate_js: [:i18n_environment, :extract_js] do
|
||||
locales = I18n.available_locales
|
||||
|
||||
if locales.empty?
|
||||
|
@ -119,17 +126,25 @@ namespace :i18n do
|
|||
exit 0
|
||||
end
|
||||
|
||||
system "./gems/canvas_i18nliner/bin/i18nliner generate_js"
|
||||
FileUtils.mkdir_p(js_translation_modules_dir)
|
||||
|
||||
if $?.exitstatus > 0
|
||||
warn "Error extracting JS translations; confirm that `./gems/canvas_i18nliner/bin/i18nliner generate_js` works"
|
||||
exit $?.exitstatus
|
||||
# emulate the old "js_bundles.json" file, which was a mapping of each scope
|
||||
# to the phrases it uses + defines
|
||||
scope_keys = JSON.parse(File.read(js_index_file)).each_with_object({}) do |phrase, acc|
|
||||
acc[phrase["scope"]] ||= []
|
||||
acc[phrase["scope"]].push(phrase["key"]) unless acc[phrase["scope"]].include?(phrase["key"])
|
||||
|
||||
if phrase.key?("used_in")
|
||||
acc[phrase["used_in"]] ||= []
|
||||
acc[phrase["used_in"]].push(phrase["key"])
|
||||
acc[phrase["used_in"]].push(phrase["key"]) unless acc[phrase["used_in"]].include?(phrase["key"])
|
||||
end
|
||||
end
|
||||
|
||||
I18nTasks::GenerateJs.new.apply(
|
||||
locales: locales,
|
||||
translations: I18n.backend.send(:translations),
|
||||
scope_keys: JSON.parse(File.read("config/locales/generated/js_bundles.json"))
|
||||
scope_keys: scope_keys
|
||||
).each do |(filename, content)|
|
||||
file = "#{js_translation_modules_dir}/#{filename}.js"
|
||||
if !File.exist?(file) || File.read(file) != content
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
"private": true,
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"gems/canvas_i18nliner",
|
||||
"gems/plugins/*",
|
||||
"packages/*",
|
||||
"ui/shared/*"
|
||||
|
@ -152,6 +151,7 @@
|
|||
"parse-link-header": "^1",
|
||||
"prop-types": "^15",
|
||||
"qs": "^6.6.0",
|
||||
"querystring": "0.2.1",
|
||||
"react": "^16.13.1",
|
||||
"react-apollo": "~3.0.1",
|
||||
"react-dnd": "^2.5.2",
|
||||
|
@ -205,6 +205,10 @@
|
|||
"@babel/preset-react": "7.14.5",
|
||||
"@babel/preset-typescript": "^7.14.5",
|
||||
"@instructure/browserslist-config-canvas-lms": ">=2",
|
||||
"@instructure/i18nliner": "^3",
|
||||
"@instructure/i18nliner-canvas": "^1.2",
|
||||
"@instructure/i18nliner-handlebars": "^2",
|
||||
"@instructure/i18nliner-runtime": "^1",
|
||||
"@prettier/plugin-ruby": "^1.5.2",
|
||||
"@sentry/webpack-plugin": "^1.5.2",
|
||||
"@sheerun/mutationobserver-shim": "0.3.2",
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
bower/**/*
|
||||
compiled/**/*
|
||||
i18nObj.js
|
||||
plugins/**/*
|
||||
symlink_to_node_modules/**/*
|
||||
translations/**/*
|
||||
vendor/**/*
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"files": [
|
||||
{
|
||||
"pattern": "**/*.js",
|
||||
"processor": "js"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -41,7 +41,6 @@ module.exports = {
|
|||
mode: 'development',
|
||||
module: {
|
||||
noParse: [
|
||||
require.resolve('@instructure/i18nliner/dist/lib/i18nliner.js'),
|
||||
require.resolve('jquery'),
|
||||
require.resolve('tinymce'),
|
||||
],
|
||||
|
|
|
@ -25,8 +25,9 @@
|
|||
const path = require('path')
|
||||
const Handlebars = require('handlebars')
|
||||
const EmberHandlebars = require('ember-template-compiler').EmberHandlebars
|
||||
const ScopedHbsExtractor = require('i18nliner-canvas/js/scoped_hbs_extractor')
|
||||
const PreProcessor = require('@instructure/i18nliner-handlebars/dist/lib/pre_processor').default
|
||||
const {readI18nScopeFromJSONFile} = require('@instructure/i18nliner-canvas/scoped_hbs_resolver')
|
||||
const ScopedHbsExtractor = require('@instructure/i18nliner-canvas/scoped_hbs_extractor')
|
||||
const ScopedHbsPreProcessor = require('@instructure/i18nliner-canvas/scoped_hbs_pre_processor')
|
||||
const { canvasDir } = require('#params')
|
||||
|
||||
function compileHandlebars(data) {
|
||||
|
@ -34,10 +35,9 @@ function compileHandlebars(data) {
|
|||
try {
|
||||
let translationCount = 0
|
||||
const ast = Handlebars.parse(source)
|
||||
const extractor = new ScopedHbsExtractor(ast, {path})
|
||||
const scope = extractor.scope
|
||||
PreProcessor.scope = scope
|
||||
PreProcessor.process(ast)
|
||||
const scope = readI18nScopeFromJSONFile(path)
|
||||
const extractor = new ScopedHbsExtractor(ast, {path, scope})
|
||||
ScopedHbsPreProcessor.processWithScope(scope, ast)
|
||||
extractor.forEach(() => translationCount++)
|
||||
|
||||
const precompiler = data.ember ? EmberHandlebars : Handlebars
|
||||
|
@ -55,11 +55,12 @@ function resourceName(path) {
|
|||
return path.replace(/^.+?\/templates\//, '').replace(/\.hbs$/, '')
|
||||
}
|
||||
|
||||
function emitTemplate(path, name, result, dependencies) {
|
||||
function emitTemplate({ name, template, dependencies }) {
|
||||
return `
|
||||
import Ember from 'ember';
|
||||
${dependencies.map(d => `import ${JSON.stringify(d)};`).join('\n')}
|
||||
const template = Ember.Handlebars.template(${result.template});
|
||||
|
||||
const template = Ember.Handlebars.template(${template});
|
||||
Ember.TEMPLATES['${name}'] = template;
|
||||
export default template;
|
||||
`
|
||||
|
@ -91,6 +92,6 @@ module.exports = function(source) {
|
|||
if (result.translationCount > 0) {
|
||||
dependencies.push('@canvas/i18n')
|
||||
}
|
||||
const compiledTemplate = emitTemplate(this.resourcePath, name, result, dependencies)
|
||||
return compiledTemplate
|
||||
|
||||
return emitTemplate({ name, template: result.template, dependencies })
|
||||
}
|
||||
|
|
|
@ -24,20 +24,13 @@
|
|||
const Handlebars = require('handlebars')
|
||||
const {pick} = require('lodash')
|
||||
const {EmberHandlebars} = require('ember-template-compiler')
|
||||
const ScopedHbsExtractor = require('i18nliner-canvas/js/scoped_hbs_extractor')
|
||||
const PreProcessor = require('@instructure/i18nliner-handlebars/dist/lib/pre_processor').default
|
||||
const ScopedHbsExtractor = require('@instructure/i18nliner-canvas/scoped_hbs_extractor')
|
||||
const ScopedHbsPreProcessor = require('@instructure/i18nliner-canvas/scoped_hbs_pre_processor')
|
||||
const {readI18nScopeFromJSONFile} = require('@instructure/i18nliner-canvas/scoped_hbs_resolver')
|
||||
const nodePath = require('path')
|
||||
const loaderUtils = require('loader-utils')
|
||||
const { canvasDir } = require('#params')
|
||||
const { contriveId, config: brandableCSSConfig } = requireBrandableCSS()
|
||||
require('i18nliner-canvas/js/scoped_hbs_pre_processor')
|
||||
|
||||
// In this main file, we do a bunch of stuff to monkey-patch the default behavior of
|
||||
// i18nliner's HbsProcessor (specifically, we set the the `directories` and define a
|
||||
// `normalizePath` function so that translation keys stay relative to canvas root dir).
|
||||
// By requiring it here the code here will use that monkeypatched behavior.
|
||||
require('i18nliner-canvas/js/main')
|
||||
|
||||
|
||||
const compileHandlebars = data => {
|
||||
const path = data.path
|
||||
|
@ -45,10 +38,9 @@ const compileHandlebars = data => {
|
|||
try {
|
||||
let translationCount = 0
|
||||
const ast = Handlebars.parse(source)
|
||||
const extractor = new ScopedHbsExtractor(ast, {path})
|
||||
const scope = extractor.scope
|
||||
PreProcessor.scope = scope
|
||||
PreProcessor.process(ast)
|
||||
const scope = readI18nScopeFromJSONFile(path)
|
||||
const extractor = new ScopedHbsExtractor(ast, {path, scope})
|
||||
ScopedHbsPreProcessor.processWithScope(scope, ast)
|
||||
extractor.forEach(() => translationCount++)
|
||||
|
||||
const precompiler = data.ember ? EmberHandlebars : Handlebars
|
||||
|
@ -60,15 +52,22 @@ const compileHandlebars = data => {
|
|||
}
|
||||
}
|
||||
|
||||
const emitTemplate = (path, name, result, dependencies, cssRegistration, partialRegistration) => {
|
||||
const emitTemplate = ({
|
||||
name,
|
||||
template,
|
||||
dependencies,
|
||||
cssRegistration,
|
||||
partialRegistration,
|
||||
}) => {
|
||||
return `
|
||||
import _Handlebars from 'handlebars/runtime';
|
||||
|
||||
var Handlebars = _Handlebars.default;
|
||||
${dependencies.map(d => `import ${JSON.stringify(d)};`).join('\n')}
|
||||
|
||||
var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {};
|
||||
var name = '${name}';
|
||||
templates[name] = template(${result.template});
|
||||
templates[name] = template(${template});
|
||||
${partialRegistration};
|
||||
${cssRegistration};
|
||||
export default templates[name];
|
||||
|
@ -169,15 +168,13 @@ function i18nLinerHandlebarsLoader(source) {
|
|||
// make sure the template has access to all our handlebars helpers
|
||||
dependencies.push('@canvas/handlebars-helpers/index.coffee')
|
||||
|
||||
const compiledTemplate = emitTemplate(
|
||||
this.resourcePath,
|
||||
return emitTemplate({
|
||||
name,
|
||||
result,
|
||||
template: result.template,
|
||||
dependencies,
|
||||
cssRegistration,
|
||||
partialRegistration
|
||||
)
|
||||
return compiledTemplate
|
||||
partialRegistration,
|
||||
})
|
||||
}
|
||||
|
||||
function requireBrandableCSS() {
|
||||
|
|
|
@ -167,7 +167,10 @@ module.exports = {
|
|||
fallback: {
|
||||
// for minimatch module; it can work without path so let webpack know
|
||||
// instead of trying to resolve node's "path"
|
||||
path: false
|
||||
path: false,
|
||||
// for parse-link-header, which requires "querystring" which is a node
|
||||
// module. btw we have at least 3 implementations of "parse-link-header"!
|
||||
querystring: require.resolve('querystring-es3')
|
||||
},
|
||||
|
||||
modules: [
|
||||
|
@ -191,8 +194,6 @@ module.exports = {
|
|||
// The files are expected to have no call to require, define or similar.
|
||||
// They are allowed to use exports and module.exports.
|
||||
noParse: [
|
||||
// i18nLiner has a `require('fs')` that it doesn't actually need, ignore it.
|
||||
require.resolve('@instructure/i18nliner/dist/lib/i18nliner.js'),
|
||||
require.resolve('jquery'),
|
||||
require.resolve('tinymce'),
|
||||
],
|
||||
|
|
|
@ -47,6 +47,8 @@ Handlebars.registerHelper name, fn for name, fn of {
|
|||
options.wrapper = wrappers if wrappers['*']
|
||||
unless (typeof this == 'undefined') || (this instanceof Window)
|
||||
options[key] = this[key] for key in this
|
||||
if options.i18n_scope
|
||||
useI18nScope(options.i18n_scope)
|
||||
new Handlebars.SafeString htmlEscape(I18nObj.t(args..., options))
|
||||
|
||||
__i18nliner_escape: (val) ->
|
||||
|
|
|
@ -16,16 +16,50 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'date-js'
|
||||
import $ from 'jquery'
|
||||
import i18nLolcalize from './i18nLolcalize'
|
||||
import I18n from 'i18n-js'
|
||||
import extend from '@instructure/i18nliner/dist/lib/extensions/i18n_js'
|
||||
import {
|
||||
extend as activateI18nliner,
|
||||
inferKey,
|
||||
normalizeDefault
|
||||
} from '@instructure/i18nliner-runtime'
|
||||
import logEagerLookupViolations from './logEagerLookupViolations'
|
||||
|
||||
import htmlEscape from 'html-escape'
|
||||
import 'date-js'
|
||||
// add i18nliner's runtime extensions to the global I18n object
|
||||
extend(I18n)
|
||||
|
||||
activateI18nliner(I18n, {
|
||||
// this is what we use elsewhere in canvas, so make i18nliner use it too
|
||||
HtmlSafeString: htmlEscape.SafeString,
|
||||
|
||||
// handle our absolute keys
|
||||
keyPattern: /^\#?\w+(\.\w+)+$/,
|
||||
|
||||
inferKey: (defaultValue, translateOptions) => (
|
||||
`#${inferKey(defaultValue, translateOptions)}`
|
||||
),
|
||||
|
||||
// when inferring the key at runtime (i.e. js/coffee or inline hbs `t`
|
||||
// call), signal to normalizeKey that it shouldn't be scoped.
|
||||
normalizeKey: (key, options) => {
|
||||
if (key[0] === '#') {
|
||||
delete options.scope
|
||||
return key.slice(1)
|
||||
}
|
||||
else if (options.scope) {
|
||||
const { scope } = options
|
||||
delete options.scope
|
||||
return `${scope}.${key}`
|
||||
}
|
||||
else {
|
||||
return key
|
||||
}
|
||||
},
|
||||
|
||||
normalizeDefault: (window.ENV && window.ENV.lolcalize) ?
|
||||
i18nLolcalize :
|
||||
normalizeDefault
|
||||
})
|
||||
|
||||
/*
|
||||
* Overridden interpolator that localizes any interpolated numbers.
|
||||
|
@ -37,10 +71,10 @@ const interpolate = I18n.interpolate.bind(I18n)
|
|||
|
||||
I18n.interpolate = function(message, origOptions) {
|
||||
const options = {...origOptions}
|
||||
const matches = message.match(this.PLACEHOLDER) || []
|
||||
const matches = message.match(I18n.placeholder) || []
|
||||
|
||||
matches.forEach(placeholder => {
|
||||
const name = placeholder.replace(this.PLACEHOLDER, '$1')
|
||||
const name = placeholder.replace(I18n.placeholder, '$1')
|
||||
if (typeof options[name] === 'number') {
|
||||
options[name] = this.localizeNumber(options[name])
|
||||
}
|
||||
|
@ -334,29 +368,6 @@ I18n.pluralize = function(count, scope, options) {
|
|||
return this.interpolate(message, options)
|
||||
}
|
||||
|
||||
I18n.Utils.HtmlSafeString = htmlEscape.SafeString // this is what we use elsewhere in canvas, so make i18nliner use it too
|
||||
I18n.CallHelpers.keyPattern = /^\#?\w+(\.\w+)+$/ // handle our absolute keys
|
||||
|
||||
// when inferring the key at runtime (i.e. js/coffee or inline hbs `t`
|
||||
// call), signal to normalizeKey that it shouldn't be scoped.
|
||||
// TODO: make i18nliner-js set i18n_inferred_key, which will DRY things up
|
||||
// slightly
|
||||
const inferKey = I18n.CallHelpers.inferKey.bind(I18n.CallHelpers)
|
||||
I18n.CallHelpers.inferKey = (defaultValue, translateOptions) =>
|
||||
`#${inferKey(defaultValue, translateOptions)}`
|
||||
|
||||
I18n.CallHelpers.normalizeKey = (key, options) => {
|
||||
if (key[0] === '#') {
|
||||
key = key.slice(1)
|
||||
delete options.scope
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
if (window.ENV && window.ENV.lolcalize) {
|
||||
I18n.CallHelpers.normalizeDefault = i18nLolcalize
|
||||
}
|
||||
|
||||
I18n.scoped = I18n.useScope = (scope, callback) => {
|
||||
const preloadLocale = window.ENV && window.ENV.LOCALE ? window.ENV.LOCALE : 'en'
|
||||
const i18n_scope = new I18n.scope(scope)
|
||||
|
|
51
yarn.lock
51
yarn.lock
|
@ -1555,24 +1555,45 @@
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.9.2"
|
||||
|
||||
"@instructure/i18nliner-handlebars@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@instructure/i18nliner-handlebars/-/i18nliner-handlebars-1.0.3.tgz#0b9f231386bbe013b855a959b35a4e2fe1542095"
|
||||
integrity sha512-4JiqZ+kQUOG6D3Df4/UQOtpG/ZDMYW4vmtkaX4Jlkef/1suvPV3JBEyL2Y9rqY/Xvx6K5wp4i6k3FPs7HeCUUw==
|
||||
"@instructure/i18nliner-canvas@^1.2":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@instructure/i18nliner-canvas/-/i18nliner-canvas-1.2.0.tgz#630165d51515ac014c71e9a2b9301ba6c53cacc5"
|
||||
integrity sha512-oIi7AY0H0g7IRMb5XEEkR1/YuKDhJ53RR4SiI7WTEz10UstLj1xD0mnXEsfdDv67uiOfDXpXWzQ0QBHFk24l2w==
|
||||
dependencies:
|
||||
"@instructure/i18nliner" "^3.0.1"
|
||||
"@instructure/i18nliner-handlebars" "^2.0.0"
|
||||
"@instructure/i18nliner-runtime" "^1"
|
||||
ember-template-compiler "1.8.0"
|
||||
glob "^7.0.3"
|
||||
handlebars "1.3.0"
|
||||
minimist "^1.1.0"
|
||||
mkdirp "^0.5.1"
|
||||
|
||||
"@instructure/i18nliner@^2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@instructure/i18nliner/-/i18nliner-2.1.0.tgz#c225797d49882b4a9b10bffc795a664d284b9e3a"
|
||||
integrity sha512-itJCIILSn68y0SjuYNnMfQvG4SvCMVxs6B25ag78lWUV3jaoWMPJ+Lc6ehjDYxI4Uyxac6IYLuyicGRxSZpJKg==
|
||||
"@instructure/i18nliner-handlebars@^2", "@instructure/i18nliner-handlebars@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@instructure/i18nliner-handlebars/-/i18nliner-handlebars-2.0.0.tgz#76d01fe4609d3247d4829b58f2439954f673399b"
|
||||
integrity sha512-y2QA1mWgYF4tg/EDmUFS74nfFo/TT6THzKtR82UFGOVxmuKz6ULCk3gH8P/o3bTgebyOdh+S9yKyzLSrZ18MbA==
|
||||
|
||||
"@instructure/i18nliner-runtime@^1":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@instructure/i18nliner-runtime/-/i18nliner-runtime-1.0.0.tgz#22f3da94fcb9369deed21bf03a04b35e9469d452"
|
||||
integrity sha512-BH5Ze6zkZG5mgKB8K4nlSla7Jfdxo5qRzgCIBRBQbnLHPayMpjRK7zAFDi6twxlOBF3Scn6JyeBT0XlMb930GQ==
|
||||
dependencies:
|
||||
crc32 "^0.2.2"
|
||||
speakingurl "^13.0.0"
|
||||
|
||||
"@instructure/i18nliner@^3", "@instructure/i18nliner@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@instructure/i18nliner/-/i18nliner-3.0.1.tgz#47d0ed1b7bd6336aa554de3b6ea981ce24f5c534"
|
||||
integrity sha512-NnryTuMZ0EPyYsIOZ8eC1S4RXqv1kQmMpHjRY/mKhSdRdV6Vpj6pdEsvLTYcWWVpVfQUVz5HQItmQIv93Oq2gg==
|
||||
dependencies:
|
||||
"@babel/parser" "^7"
|
||||
"@babel/traverse" "^7"
|
||||
"@instructure/i18nliner-runtime" "^1"
|
||||
cli-color "^1"
|
||||
crc32 "~0.2.2"
|
||||
gglobby "0.0.3"
|
||||
minimist "~1.2.0"
|
||||
mkdirp "~0.5.1"
|
||||
speakingurl "13.0.0"
|
||||
|
||||
"@instructure/instructure-theme@^7.14.0":
|
||||
version "7.14.0"
|
||||
|
@ -8920,7 +8941,7 @@ coffee-loader@~0.7.2:
|
|||
dependencies:
|
||||
loader-utils "^1.0.2"
|
||||
|
||||
coffee-script@^1, coffee-script@^1.12.4:
|
||||
coffee-script@^1:
|
||||
version "1.12.7"
|
||||
resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53"
|
||||
integrity sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==
|
||||
|
@ -9424,7 +9445,7 @@ cpy@^8.1.1:
|
|||
p-filter "^2.1.0"
|
||||
p-map "^3.0.0"
|
||||
|
||||
crc32@^0.2.2, crc32@~0.2.2:
|
||||
crc32@^0.2.2:
|
||||
version "0.2.2"
|
||||
resolved "https://registry.yarnpkg.com/crc32/-/crc32-0.2.2.tgz#7ad220d6ffdcd119f9fc127a7772cacea390a4ba"
|
||||
integrity sha1-etIg1v/c0Rn5/BJ6d3LKzqOQpLo=
|
||||
|
@ -20811,7 +20832,7 @@ querystring@0.2.0:
|
|||
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
|
||||
integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
|
||||
|
||||
querystring@^0.2.0:
|
||||
querystring@0.2.1, querystring@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd"
|
||||
integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==
|
||||
|
@ -23176,10 +23197,10 @@ spdx-license-ids@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz#8a595135def9592bda69709474f1cbeea7c2467f"
|
||||
integrity sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ==
|
||||
|
||||
speakingurl@13.0.0:
|
||||
speakingurl@^13.0.0:
|
||||
version "13.0.0"
|
||||
resolved "https://registry.yarnpkg.com/speakingurl/-/speakingurl-13.0.0.tgz#266c52d2b47585375af058e4783c8d1097a2d3db"
|
||||
integrity sha1-JmxS0rR1hTda8FjkeDyNEJei09s=
|
||||
integrity sha512-bjwu8erR5rsfHW9ZKHReHJt9CEPrJCZopJ3rMVcCh8buJom+BI/7bHWYiHSZd+yS9rx4DGN+r5oKoVqDkiqfWw==
|
||||
|
||||
specificity@^0.4.1:
|
||||
version "0.4.1"
|
||||
|
|
Loading…
Reference in New Issue