diff --git a/src/builtin-addons/core/intl-utils.ts b/src/builtin-addons/core/intl-utils.ts index 1f9b9c85..40108cc9 100644 --- a/src/builtin-addons/core/intl-utils.ts +++ b/src/builtin-addons/core/intl-utils.ts @@ -6,6 +6,7 @@ import { Location, Range } from 'vscode-languageserver'; import { URI } from 'vscode-uri'; import { Server } from '../..'; import { logDebugInfo } from '../../utils/logger'; +import { getRequireSupport } from '../../utils/layout-helpers'; type Translations = { locale: string; @@ -14,6 +15,10 @@ type Translations = { }; type TranslationsHashMap = Record; +type EmberIntlConfig = { + wrapTranslationsWithNamespace?: boolean; +}; + type TranslationFile = | { type: 'json'; @@ -30,6 +35,29 @@ type TranslationFile = const YAML_EXTENSIONS = ['.yaml', '.yml']; +export function getEmberIntlConfig(root: string): EmberIntlConfig | null { + try { + if (!getRequireSupport()) { + return null; + } + + // @ts-expect-error @todo - fix webpack imports + const requireFunc = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require; + + // eslint-disable-next-line @typescript-eslint/no-var-requires + const intlConfig = requireFunc(path.join(root, 'config', 'ember-intl.js')); + + if (!intlConfig) { + return null; + } + + return typeof intlConfig === 'function' ? intlConfig() : intlConfig; + } catch (e) { + // logDebugInfo('catch', e); + return null; + } +} + export async function getTranslations(root: string, server: Server): Promise { const hashMap = {}; const intlEntry = path.join(root, 'translations'); @@ -37,13 +65,13 @@ export async function getTranslations(root: string, server: Server): Promise = flat(translationFile.json); const extension = path.extname(filePath); Object.keys(items).forEach((p) => { - if (!(p in hash)) { - hash[p] = []; + const key = generateTranslationKey(p, filePath, root); + + if (!(key in hash)) { + hash[key] = []; } const uri = URI.file(filePath).toString(); @@ -124,7 +170,7 @@ function addToHashMap(hash: TranslationsHashMap, translationFile: TranslationFil const endColumn = position ? position.end.column - 1 : 0; const range = Range.create(startLine, startColumn, endLine, endColumn); - hash[p].push({ locale, text: items[p], location: Location.create(uri, range) }); + hash[key].push({ locale, text: items[p], location: Location.create(uri, range) }); }); } diff --git a/test/bultin-addons/core/intl-providers-test.ts b/test/bultin-addons/core/intl-providers-test.ts index 224017b9..2fb3ebd1 100644 --- a/test/bultin-addons/core/intl-providers-test.ts +++ b/test/bultin-addons/core/intl-providers-test.ts @@ -11,6 +11,14 @@ const translations = { "rootFileTranslation": "text 1 in polish" }`, 'sub-folder': { + 'sub-sub-folder': { + 'en-us.json': `{ + "subsubFolderTranslation": { + "subSubTranslation": "text 3", + "anotherSubSubTranslation": "text 4" + } + }`, + }, 'en-us.json': `{ "subFolderTranslation": { "subTranslation": "text 2", @@ -236,161 +244,522 @@ for (const asyncFsEnabled of testCaseAsyncFsOptions) { ]); }); - it('should autocomplete sub folder translation in handlebars', async () => { - expect( - ( - await getResult( - CompletionRequest.method, - connection, + describe('With ember-intl config', () => { + describe('#wrapTranslationsWithNamespace is true', () => { + it('should autocomplete sub folder translation in handlebars', async () => { + expect( + ( + await getResult( + CompletionRequest.method, + connection, + { + app: { + components: { + 'test.hbs': `{{t "subFolderTranslat" }}`, + }, + }, + config: { + 'ember-intl.js': `module.exports = function() { return { wrapTranslationsWithNamespace: true } }`, + }, + translations, + }, + 'app/components/test.hbs', + { line: 0, character: 12 } + ) + ).response + ).toEqual([ { - app: { - components: { - 'test.hbs': `{{t "subFolderTranslat" }}`, + documentation: 'en-us : text 2', + kind: 12, + label: 'sub-folder.subFolderTranslation.subTranslation', + textEdit: { + newText: 'sub-folder.subFolderTranslation.subTranslation', + range: { + end: { + character: 5, + line: 0, + }, + start: { + character: 5, + line: 0, + }, }, }, - translations, }, - 'app/components/test.hbs', - { line: 0, character: 12 } - ) - ).response - ).toEqual([ - { - documentation: 'en-us : text 2', - kind: 12, - label: 'subFolderTranslation.subTranslation', - textEdit: { - newText: 'subFolderTranslation.subTranslation', - range: { - end: { - character: 5, - line: 0, + { + documentation: 'en-us : another text', + kind: 12, + label: 'sub-folder.subFolderTranslation.anotherTranslation', + textEdit: { + newText: 'sub-folder.subFolderTranslation.anotherTranslation', + range: { + end: { + character: 5, + line: 0, + }, + start: { + character: 5, + line: 0, + }, + }, }, - start: { - character: 5, - line: 0, + }, + { + documentation: 'en-us : text 3', + kind: 12, + label: 'sub-folder.sub-sub-folder.subsubFolderTranslation.subSubTranslation', + textEdit: { + newText: 'sub-folder.sub-sub-folder.subsubFolderTranslation.subSubTranslation', + range: { + end: { + character: 5, + line: 0, + }, + start: { + character: 5, + line: 0, + }, + }, }, }, - }, - }, - { - documentation: 'en-us : another text', - kind: 12, - label: 'subFolderTranslation.anotherTranslation', - textEdit: { - newText: 'subFolderTranslation.anotherTranslation', - range: { - end: { - character: 5, - line: 0, + { + documentation: 'en-us : text 4', + kind: 12, + label: 'sub-folder.sub-sub-folder.subsubFolderTranslation.anotherSubSubTranslation', + textEdit: { + newText: 'sub-folder.sub-sub-folder.subsubFolderTranslation.anotherSubSubTranslation', + range: { + end: { + character: 5, + line: 0, + }, + start: { + character: 5, + line: 0, + }, + }, }, - start: { - character: 5, - line: 0, + }, + ]); + }); + + it('should autocomplete in JS files when in the end of expression', async () => { + expect( + ( + await getResult( + CompletionRequest.method, + connection, + { + app: { + components: { + 'test.js': 'export default class Foo extends Bar { text = this.intl.t("subFolderTranslation.another"); }', + }, + }, + config: { + 'ember-intl.js': `module.exports = function() { return { wrapTranslationsWithNamespace: true } }`, + }, + translations, + }, + 'app/components/test.js', + { line: 0, character: 86 } + ) + ).response + ).toEqual([ + { + documentation: 'en-us : another text', + kind: 12, + label: 'sub-folder.subFolderTranslation.anotherTranslation', + textEdit: { + newText: 'sub-folder.subFolderTranslation.anotherTranslation', + range: { + end: { + character: 59, + line: 0, + }, + start: { + character: 59, + line: 0, + }, + }, }, }, - }, - }, - ]); - }); + { + documentation: 'en-us : text 4', + kind: 12, + label: 'sub-folder.sub-sub-folder.subsubFolderTranslation.anotherSubSubTranslation', + textEdit: { + newText: 'sub-folder.sub-sub-folder.subsubFolderTranslation.anotherSubSubTranslation', + range: { + end: { + character: 59, + line: 0, + }, + start: { + character: 59, + line: 0, + }, + }, + }, + }, + ]); + }); - it('should autocomplete in JS files when in the end of expression', async () => { - expect( - ( - await getResult( - CompletionRequest.method, - connection, + it('should autocomplete sub folder translation in JS', async () => { + expect( + ( + await getResult( + CompletionRequest.method, + connection, + { + app: { + components: { + 'test.js': `export default class Foo extends Bar { text = this.intl.t("subFolderTranslation."); }`, + }, + }, + config: { + 'ember-intl.js': `module.exports = function() { return { wrapTranslationsWithNamespace: true } }`, + }, + translations, + }, + 'app/components/test.js', + { line: 0, character: 64 } + ) + ).response + ).toEqual([ { - app: { - components: { - 'test.js': 'export default class Foo extends Bar { text = this.intl.t("subFolderTranslation.another"); }', + documentation: 'en-us : text 2', + kind: 12, + label: 'sub-folder.subFolderTranslation.subTranslation', + textEdit: { + newText: 'sub-folder.subFolderTranslation.subTranslation', + range: { + end: { + character: 59, + line: 0, + }, + start: { + character: 59, + line: 0, + }, }, }, - translations, }, - 'app/components/test.js', - { line: 0, character: 86 } - ) - ).response - ).toEqual([ - { - documentation: 'en-us : another text', - kind: 12, - label: 'subFolderTranslation.anotherTranslation', - textEdit: { - newText: 'subFolderTranslation.anotherTranslation', - range: { - end: { - character: 59, - line: 0, + { + documentation: 'en-us : another text', + kind: 12, + label: 'sub-folder.subFolderTranslation.anotherTranslation', + textEdit: { + newText: 'sub-folder.subFolderTranslation.anotherTranslation', + range: { + end: { + character: 59, + line: 0, + }, + start: { + character: 59, + line: 0, + }, + }, }, - start: { - character: 59, - line: 0, + }, + { + documentation: 'en-us : text 3', + kind: 12, + label: 'sub-folder.sub-sub-folder.subsubFolderTranslation.subSubTranslation', + textEdit: { + newText: 'sub-folder.sub-sub-folder.subsubFolderTranslation.subSubTranslation', + range: { + end: { + character: 59, + line: 0, + }, + start: { + character: 59, + line: 0, + }, + }, }, }, - }, - }, - ]); - }); + { + documentation: 'en-us : text 4', + kind: 12, + label: 'sub-folder.sub-sub-folder.subsubFolderTranslation.anotherSubSubTranslation', + textEdit: { + newText: 'sub-folder.sub-sub-folder.subsubFolderTranslation.anotherSubSubTranslation', + range: { + end: { + character: 59, + line: 0, + }, + start: { + character: 59, + line: 0, + }, + }, + }, + }, + ]); + }); + }); - it('should autocomplete sub folder translation in JS', async () => { - expect( - ( - await getResult( - CompletionRequest.method, - connection, + describe('#wrapTranslationsWithNamespace is not set', () => { + it('should not autocomplete sub folder translation in handlebars', async () => { + expect( + ( + await getResult( + CompletionRequest.method, + connection, + { + app: { + components: { + 'test.hbs': `{{t "subFolderTranslat" }}`, + }, + }, + config: { + 'ember-intl.js': `module.exports = function() { }`, + }, + translations, + }, + 'app/components/test.hbs', + { line: 0, character: 12 } + ) + ).response + ).toEqual([ { - app: { - components: { - 'test.js': `export default class Foo extends Bar { text = this.intl.t("subFolderTranslation."); }`, + documentation: 'en-us : text 2', + kind: 12, + label: 'subFolderTranslation.subTranslation', + textEdit: { + newText: 'subFolderTranslation.subTranslation', + range: { + end: { + character: 5, + line: 0, + }, + start: { + character: 5, + line: 0, + }, }, }, - translations, }, - 'app/components/test.js', - { line: 0, character: 64 } - ) - ).response - ).toEqual([ - { - documentation: 'en-us : text 2', - kind: 12, - label: 'subFolderTranslation.subTranslation', - textEdit: { - newText: 'subFolderTranslation.subTranslation', - range: { - end: { - character: 59, - line: 0, + { + documentation: 'en-us : another text', + kind: 12, + label: 'subFolderTranslation.anotherTranslation', + textEdit: { + newText: 'subFolderTranslation.anotherTranslation', + range: { + end: { + character: 5, + line: 0, + }, + start: { + character: 5, + line: 0, + }, + }, }, - start: { - character: 59, - line: 0, + }, + { + documentation: 'en-us : text 3', + kind: 12, + label: 'subsubFolderTranslation.subSubTranslation', + textEdit: { + newText: 'subsubFolderTranslation.subSubTranslation', + range: { + end: { + character: 5, + line: 0, + }, + start: { + character: 5, + line: 0, + }, + }, }, }, - }, - }, - { - documentation: 'en-us : another text', - kind: 12, - label: 'subFolderTranslation.anotherTranslation', - textEdit: { - newText: 'subFolderTranslation.anotherTranslation', - range: { - end: { - character: 59, - line: 0, + { + documentation: 'en-us : text 4', + kind: 12, + label: 'subsubFolderTranslation.anotherSubSubTranslation', + textEdit: { + newText: 'subsubFolderTranslation.anotherSubSubTranslation', + range: { + end: { + character: 5, + line: 0, + }, + start: { + character: 5, + line: 0, + }, + }, }, - start: { - character: 59, - line: 0, + }, + ]); + }); + + it('should autocomplete in JS files when in the end of expression', async () => { + expect( + ( + await getResult( + CompletionRequest.method, + connection, + { + app: { + components: { + 'test.js': 'export default class Foo extends Bar { text = this.intl.t("subFolderTranslation.another"); }', + }, + }, + config: { + 'ember-intl.js': `module.exports = function() { return {}; }`, + }, + translations, + }, + 'app/components/test.js', + { line: 0, character: 86 } + ) + ).response + ).toEqual([ + { + documentation: 'en-us : another text', + kind: 12, + label: 'subFolderTranslation.anotherTranslation', + textEdit: { + newText: 'subFolderTranslation.anotherTranslation', + range: { + end: { + character: 59, + line: 0, + }, + start: { + character: 59, + line: 0, + }, + }, }, }, - }, - }, - ]); + { + documentation: 'en-us : text 4', + kind: 12, + label: 'subsubFolderTranslation.anotherSubSubTranslation', + textEdit: { + newText: 'subsubFolderTranslation.anotherSubSubTranslation', + range: { + end: { + character: 59, + line: 0, + }, + start: { + character: 59, + line: 0, + }, + }, + }, + }, + ]); + }); + + it('should autocomplete sub folder translation in JS', async () => { + expect( + ( + await getResult( + CompletionRequest.method, + connection, + { + app: { + components: { + 'test.js': `export default class Foo extends Bar { text = this.intl.t("subFolderTranslation."); }`, + }, + }, + config: { + 'ember-intl.js': `module.exports = function() { return {}; }`, + }, + translations, + }, + 'app/components/test.js', + { line: 0, character: 64 } + ) + ).response + ).toEqual([ + { + documentation: 'en-us : text 2', + kind: 12, + label: 'subFolderTranslation.subTranslation', + textEdit: { + newText: 'subFolderTranslation.subTranslation', + range: { + end: { + character: 59, + line: 0, + }, + start: { + character: 59, + line: 0, + }, + }, + }, + }, + { + documentation: 'en-us : another text', + kind: 12, + label: 'subFolderTranslation.anotherTranslation', + textEdit: { + newText: 'subFolderTranslation.anotherTranslation', + range: { + end: { + character: 59, + line: 0, + }, + start: { + character: 59, + line: 0, + }, + }, + }, + }, + { + documentation: 'en-us : text 3', + kind: 12, + label: 'subsubFolderTranslation.subSubTranslation', + textEdit: { + newText: 'subsubFolderTranslation.subSubTranslation', + range: { + end: { + character: 59, + line: 0, + }, + start: { + character: 59, + line: 0, + }, + }, + }, + }, + { + documentation: 'en-us : text 4', + kind: 12, + label: 'subsubFolderTranslation.anotherSubSubTranslation', + textEdit: { + newText: 'subsubFolderTranslation.anotherSubSubTranslation', + range: { + end: { + character: 59, + line: 0, + }, + start: { + character: 59, + line: 0, + }, + }, + }, + }, + ]); + }); + }); }); + it('should autocomplete translation base on translation text', async () => { expect( (