apostrophe 3.63.3 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +13 -4
- package/CHANGELOG.md +37 -5
- package/defaults.js +2 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/apps/AposAdminBar.js +7 -17
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBar.vue +14 -16
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +1 -1
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarMenu.vue +22 -15
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarUser.vue +2 -2
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +13 -8
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextModeAndSettings.vue +18 -10
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue +4 -4
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextUndoRedo.vue +14 -8
- package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposSavingIndicator.vue +2 -1
- package/modules/@apostrophecms/area/ui/apos/apps/AposAreas.js +36 -54
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaContextualMenu.vue +20 -25
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +5 -12
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaExpandedMenu.vue +11 -3
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaMenu.vue +6 -6
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaMenuItem.vue +3 -2
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +31 -44
- package/modules/@apostrophecms/area/ui/apos/components/AposWidgetControls.vue +16 -16
- package/modules/@apostrophecms/asset/index.js +25 -12
- package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.config.js +3 -3
- package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.vue.js +7 -1
- package/modules/@apostrophecms/attachment/index.js +2 -1
- package/modules/@apostrophecms/attachment/public/img/missing-icon.svg +14 -0
- package/modules/@apostrophecms/busy/ui/apos/apps/AposBusy.js +8 -7
- package/modules/@apostrophecms/busy/ui/apos/components/TheAposBusy.vue +1 -1
- package/modules/@apostrophecms/command-menu/ui/apos/apps/AposCommandMenu.js +11 -29
- package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuKey.vue +1 -1
- package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuShortcut.vue +6 -6
- package/modules/@apostrophecms/command-menu/ui/apos/components/TheAposCommandMenu.vue +10 -7
- package/modules/@apostrophecms/doc-type/index.js +34 -13
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocContextMenu.vue +3 -3
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +20 -15
- package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +1 -1
- package/modules/@apostrophecms/i18n/i18n/en.json +13 -0
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +209 -33
- package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalizeErrors.vue +3 -3
- package/modules/@apostrophecms/image/ui/apos/components/AposImageCropper.vue +5 -5
- package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +6 -6
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +23 -16
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerDisplay.vue +11 -11
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +28 -21
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerSelections.vue +4 -3
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploader.vue +5 -4
- package/modules/@apostrophecms/login/index.js +18 -1
- package/modules/@apostrophecms/login/ui/apos/apps/AposLogin.js +6 -7
- package/modules/@apostrophecms/login/ui/apos/components/AposForgotPasswordForm.vue +3 -3
- package/modules/@apostrophecms/login/ui/apos/components/AposLoginForm.vue +10 -10
- package/modules/@apostrophecms/login/ui/apos/components/AposResetPasswordForm.vue +3 -3
- package/modules/@apostrophecms/login/ui/apos/components/TheAposLogin.vue +2 -2
- package/modules/@apostrophecms/login/ui/apos/logic/AposLoginForm.js +1 -16
- package/modules/@apostrophecms/modal/ui/apos/apps/AposModals.js +60 -87
- package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +15 -11
- package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +17 -11
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalBreadcrumbs.vue +7 -4
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalConfirm.vue +9 -9
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalLip.vue +2 -2
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalShareDraft.vue +8 -8
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalTabs.vue +4 -3
- package/modules/@apostrophecms/modal/ui/apos/components/AposModalToolbar.vue +7 -7
- package/modules/@apostrophecms/modal/ui/apos/components/TheAposModals.vue +22 -4
- package/modules/@apostrophecms/modal/ui/apos/composables/AposFocus.js +95 -0
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposDocsManagerMixin.js +2 -3
- package/modules/@apostrophecms/modal/ui/apos/mixins/AposEditorMixin.js +6 -0
- package/modules/@apostrophecms/notification/index.js +4 -4
- package/modules/@apostrophecms/notification/ui/apos/apps/AposNotification.js +6 -9
- package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +12 -8
- package/modules/@apostrophecms/notification/ui/apos/components/TheAposNotifications.vue +4 -2
- package/modules/@apostrophecms/oembed-field/ui/apos/components/AposInputOembed.vue +12 -10
- package/modules/@apostrophecms/page/index.js +1 -0
- package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +15 -11
- package/modules/@apostrophecms/page/ui/apos/logic/AposPagesManager.js +1 -1
- package/modules/@apostrophecms/permission/ui/apos/components/AposInputRole.vue +3 -3
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +25 -17
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +24 -20
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerSelectBox.vue +9 -5
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposRelationshipEditor.vue +15 -11
- package/modules/@apostrophecms/rich-text-widget/index.js +1 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposImageControlDialog.vue +7 -6
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +31 -30
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapAnchor.vue +12 -10
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapButton.vue +1 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapImage.vue +1 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapLink.vue +9 -8
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapStyles.vue +3 -3
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapTable.vue +3 -3
- package/modules/@apostrophecms/schema/index.js +69 -8
- package/modules/@apostrophecms/schema/lib/addFieldTypes.js +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposArrayEditor.vue +10 -8
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +5 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +81 -277
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputAttachment.vue +4 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputBoolean.vue +24 -14
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputCheckboxes.vue +7 -6
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputColor.vue +10 -7
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputObject.vue +3 -3
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputPassword.vue +6 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRadio.vue +5 -4
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRange.vue +9 -6
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputRelationship.vue +15 -12
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSelect.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputSlug.vue +16 -12
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputString.vue +19 -11
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +15 -12
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +75 -22
- package/modules/@apostrophecms/schema/ui/apos/components/AposSearchList.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposSubform.vue +2 -2
- package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +0 -4
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArea.js +3 -3
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +15 -4
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputAttachment.js +3 -3
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputCheckboxes.js +7 -7
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputColor.js +5 -8
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputDateAndTime.js +1 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputObject.js +1 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRadio.js +1 -1
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +12 -9
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputSelect.js +3 -3
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputString.js +7 -9
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputWrapper.js +4 -4
- package/modules/@apostrophecms/schema/ui/apos/logic/AposSchema.js +42 -13
- package/modules/@apostrophecms/schema/ui/apos/logic/AposSubform.js +1 -1
- package/modules/@apostrophecms/schema/ui/apos/mixins/AposInputMixin.js +9 -9
- package/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss +205 -0
- package/modules/@apostrophecms/settings/ui/apos/components/AposSettingsManager.vue +5 -5
- package/modules/@apostrophecms/settings/ui/apos/logic/AposSettingsManager.js +4 -4
- package/modules/@apostrophecms/submitted-draft/ui/apos/components/AposSubmittedDraftIcon.vue +5 -4
- package/modules/@apostrophecms/task/index.js +2 -0
- package/modules/@apostrophecms/translation/index.js +233 -0
- package/modules/@apostrophecms/translation/ui/apos/components/AposTranslationIndicator.vue +84 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposAvatar.vue +2 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +4 -4
- package/modules/@apostrophecms/ui/ui/apos/components/AposButtonGroup.vue +6 -6
- package/modules/@apostrophecms/ui/ui/apos/components/AposButtonSplit.vue +120 -113
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellButton.vue +2 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellLabels.vue +49 -5
- package/modules/@apostrophecms/ui/ui/apos/components/AposCheckbox.vue +19 -19
- package/modules/@apostrophecms/ui/ui/apos/components/AposCloudUploadIcon.vue +10 -5
- package/modules/@apostrophecms/ui/ui/apos/components/AposCombo.vue +15 -15
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +214 -191
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuDialog.vue +77 -65
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuItem.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuTip.vue +28 -50
- package/modules/@apostrophecms/ui/ui/apos/components/AposEmptyState.vue +3 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +5 -5
- package/modules/@apostrophecms/ui/ui/apos/components/AposFilterMenu.vue +4 -4
- package/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposLabel.vue +1 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposMinMaxCount.vue +5 -5
- package/modules/@apostrophecms/ui/ui/apos/components/AposPager.vue +14 -8
- package/modules/@apostrophecms/ui/ui/apos/components/AposPagerDots.vue +2 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlat.vue +13 -12
- package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +53 -59
- package/modules/@apostrophecms/ui/ui/apos/components/AposSpinner.vue +2 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposSubformPreview.vue +2 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposTag.vue +3 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposTagApply.vue +40 -35
- package/modules/@apostrophecms/ui/ui/apos/components/AposTagListItem.vue +2 -1
- package/modules/@apostrophecms/ui/ui/apos/components/AposToggle.vue +2 -2
- package/modules/@apostrophecms/ui/ui/apos/components/AposTree.vue +9 -11
- package/modules/@apostrophecms/ui/ui/apos/components/AposTreeHeader.vue +5 -3
- package/modules/@apostrophecms/ui/ui/apos/components/AposTreeRows.vue +129 -129
- package/modules/@apostrophecms/ui/ui/apos/composables/AposTheme.js +11 -0
- package/modules/@apostrophecms/ui/ui/apos/lib/click-outside-element.js +4 -4
- package/modules/@apostrophecms/ui/ui/apos/lib/i18next.js +56 -50
- package/modules/@apostrophecms/ui/ui/apos/lib/tooltip.js +191 -0
- package/modules/@apostrophecms/ui/ui/apos/lib/vue.js +19 -10
- package/modules/@apostrophecms/ui/ui/apos/mixins/AposAdvisoryLockMixin.js +1 -1
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +1 -1
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +1 -0
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_tooltips.scss +6 -22
- package/modules/@apostrophecms/ui/ui/apos/scss/shared/_table-rows.scss +1 -1
- package/modules/@apostrophecms/widget-type/index.js +8 -2
- package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +26 -22
- package/modules/@apostrophecms/widget-type/ui/apos/mixins/AposWidgetMixin.js +4 -4
- package/package.json +31 -44
- package/test/attachments.js +5 -0
- package/test/schemas.js +138 -0
- package/test/translation.js +538 -0
- package/test-lib/util.js +21 -0
- package/modules/@apostrophecms/ui/ui/apos/lib/localized-v-tooltip.js +0 -63
- package/modules/@apostrophecms/ui/ui/apos/lib/tooltip-options.js +0 -13
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert/strict');
|
|
3
|
+
|
|
4
|
+
describe('Translation', function () {
|
|
5
|
+
let apos = null;
|
|
6
|
+
|
|
7
|
+
this.timeout(t.timeout);
|
|
8
|
+
|
|
9
|
+
const boot = async function () {
|
|
10
|
+
apos = await t.create({
|
|
11
|
+
root: module,
|
|
12
|
+
modules: {
|
|
13
|
+
'fake-translation-provider': {
|
|
14
|
+
options: {
|
|
15
|
+
alias: 'fakeProvider'
|
|
16
|
+
},
|
|
17
|
+
methods: (self) => ({
|
|
18
|
+
async translate(req, provider, doc, source, target) {
|
|
19
|
+
doc.title = `${provider}-${source}-${target}-${doc.title}-translated`;
|
|
20
|
+
return doc;
|
|
21
|
+
},
|
|
22
|
+
async getSupportedLanguages(req, {
|
|
23
|
+
provider, source, target
|
|
24
|
+
} = {}) {
|
|
25
|
+
const supported = [ 'en', 'es', 'fr' ];
|
|
26
|
+
const sourceRespone = source?.length
|
|
27
|
+
? source.map((code) => ({
|
|
28
|
+
code,
|
|
29
|
+
supported: supported.includes(code)
|
|
30
|
+
}))
|
|
31
|
+
: supported.map((code) => ({
|
|
32
|
+
code,
|
|
33
|
+
supported: true
|
|
34
|
+
}));
|
|
35
|
+
const targetResponse = target?.length
|
|
36
|
+
? target.map((code) => ({
|
|
37
|
+
code,
|
|
38
|
+
supported: supported.includes(code)
|
|
39
|
+
}))
|
|
40
|
+
: supported.map((code) => ({
|
|
41
|
+
code,
|
|
42
|
+
supported: true
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
source: sourceRespone,
|
|
47
|
+
target: targetResponse
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
},
|
|
52
|
+
'default-page': {
|
|
53
|
+
extend: '@apostrophecms/page-type',
|
|
54
|
+
options: {
|
|
55
|
+
label: 'Default'
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
before(async function () {
|
|
63
|
+
await boot();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
after(function () {
|
|
67
|
+
return t.destroy(apos);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should be disabled if no providers', async function () {
|
|
71
|
+
const translation = apos.modules['@apostrophecms/translation'];
|
|
72
|
+
assert.equal(translation.isEnabled(), false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should add a translation provider', async function () {
|
|
76
|
+
const translation = apos.modules['@apostrophecms/translation'];
|
|
77
|
+
const provider = apos.fakeProvider;
|
|
78
|
+
|
|
79
|
+
translation.addProvider(provider, {
|
|
80
|
+
label: 'Fake Provider',
|
|
81
|
+
name: 'fake'
|
|
82
|
+
});
|
|
83
|
+
assert.equal(translation.providers.length, 1);
|
|
84
|
+
assert.equal(translation.providers[0].name, 'fake');
|
|
85
|
+
assert.equal(translation.isEnabled(), true);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should validate the provider interface', async function () {
|
|
89
|
+
const translation = apos.modules['@apostrophecms/translation'];
|
|
90
|
+
|
|
91
|
+
assert.throws(() => translation.addProvider(), {
|
|
92
|
+
cause: 'invalidArguments'
|
|
93
|
+
});
|
|
94
|
+
assert.throws(() => translation.addProvider({}, { name: 'anotherFake' }), {
|
|
95
|
+
cause: 'invalidArguments'
|
|
96
|
+
});
|
|
97
|
+
assert.throws(
|
|
98
|
+
() => translation.addProvider(
|
|
99
|
+
{ __meta: { name: 'another-translation-provider' } },
|
|
100
|
+
{ name: 'anotherFake' }
|
|
101
|
+
),
|
|
102
|
+
{ cause: 'interfaceNotImplemented' }
|
|
103
|
+
);
|
|
104
|
+
assert.throws(
|
|
105
|
+
() => translation.addProvider(
|
|
106
|
+
{
|
|
107
|
+
__meta: { name: 'another-translation-provider' },
|
|
108
|
+
translate() {}
|
|
109
|
+
},
|
|
110
|
+
{ name: 'anotherFake' }
|
|
111
|
+
),
|
|
112
|
+
{ cause: 'interfaceNotImplemented' }
|
|
113
|
+
);
|
|
114
|
+
assert.throws(
|
|
115
|
+
() => translation.addProvider(
|
|
116
|
+
{
|
|
117
|
+
__meta: { name: 'another-translation-provider' },
|
|
118
|
+
getSupportedLanguages() {}
|
|
119
|
+
},
|
|
120
|
+
{ name: 'anotherFake' }
|
|
121
|
+
),
|
|
122
|
+
{ cause: 'interfaceNotImplemented' }
|
|
123
|
+
);
|
|
124
|
+
assert.throws(
|
|
125
|
+
() => translation.addProvider(
|
|
126
|
+
{
|
|
127
|
+
__meta: { name: 'another-translation-provider' },
|
|
128
|
+
translate() {},
|
|
129
|
+
getSupportedLanguages() {}
|
|
130
|
+
},
|
|
131
|
+
{ name: 'fake' }
|
|
132
|
+
),
|
|
133
|
+
{ cause: 'duplicate' }
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should get a translation provider by its name', async function () {
|
|
138
|
+
const translation = apos.modules['@apostrophecms/translation'];
|
|
139
|
+
|
|
140
|
+
assert.deepEqual(translation.getProvider('fake'), {
|
|
141
|
+
name: 'fake',
|
|
142
|
+
label: 'Fake Provider',
|
|
143
|
+
module: 'fake-translation-provider'
|
|
144
|
+
});
|
|
145
|
+
assert.equal(translation.getProvider('anotherFake'), undefined);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should get the first provider if no name is provided', async function () {
|
|
149
|
+
const translation = apos.modules['@apostrophecms/translation'];
|
|
150
|
+
|
|
151
|
+
assert.deepEqual(translation.getProvider(), {
|
|
152
|
+
name: 'fake',
|
|
153
|
+
label: 'Fake Provider',
|
|
154
|
+
module: 'fake-translation-provider'
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should get the provider module by its name', async function () {
|
|
159
|
+
const translation = apos.modules['@apostrophecms/translation'];
|
|
160
|
+
|
|
161
|
+
assert.deepEqual(translation.getProviderModule('fake'), apos.fakeProvider);
|
|
162
|
+
assert.equal(translation.getProviderModule('anotherFake'), undefined);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should get the first provider module if no name is provided', async function () {
|
|
166
|
+
const translation = apos.modules['@apostrophecms/translation'];
|
|
167
|
+
|
|
168
|
+
assert.deepEqual(translation.getProviderModule(), apos.fakeProvider);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should get browser data', async function () {
|
|
172
|
+
const translation = apos.modules['@apostrophecms/translation'];
|
|
173
|
+
const req = apos.task.getReq();
|
|
174
|
+
|
|
175
|
+
assert.deepEqual(translation.getBrowserData(req), {
|
|
176
|
+
action: '/api/v1/@apostrophecms/translation',
|
|
177
|
+
enabled: true,
|
|
178
|
+
providers: [
|
|
179
|
+
{
|
|
180
|
+
name: 'fake',
|
|
181
|
+
label: 'Fake Provider'
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should invoke translation on beforeLocalize', async function () {
|
|
188
|
+
const translation = apos.modules['@apostrophecms/translation'];
|
|
189
|
+
const handler = translation.handlers['@apostrophecms/doc-type:beforeLocalize']
|
|
190
|
+
.translate;
|
|
191
|
+
|
|
192
|
+
assert(handler);
|
|
193
|
+
|
|
194
|
+
const req = apos.task.getReq({
|
|
195
|
+
query: {
|
|
196
|
+
aposTranslateTargets: [ 'es', 'fr' ]
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
const doc = {
|
|
200
|
+
_id: '123',
|
|
201
|
+
title: 'Hello World'
|
|
202
|
+
};
|
|
203
|
+
const source = 'en';
|
|
204
|
+
const target = 'es';
|
|
205
|
+
const existing = false;
|
|
206
|
+
|
|
207
|
+
await handler(req, doc, {
|
|
208
|
+
source,
|
|
209
|
+
target,
|
|
210
|
+
existing
|
|
211
|
+
});
|
|
212
|
+
assert.equal(doc.title, 'fake-en-es-Hello World-translated');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should skip translation on beforeLocalize if no providers', async function () {
|
|
216
|
+
const translation = apos.modules['@apostrophecms/translation'];
|
|
217
|
+
const providers = translation.providers;
|
|
218
|
+
translation.providers = [];
|
|
219
|
+
const handler = translation.handlers['@apostrophecms/doc-type:beforeLocalize']
|
|
220
|
+
.translate;
|
|
221
|
+
|
|
222
|
+
const savedNotify = apos.notify;
|
|
223
|
+
let notifyCallArgs = null;
|
|
224
|
+
apos.notify = function (...args) {
|
|
225
|
+
notifyCallArgs = args;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
assert(handler);
|
|
229
|
+
|
|
230
|
+
const req = apos.task.getReq({
|
|
231
|
+
query: {
|
|
232
|
+
aposTranslateTargets: [ 'es', 'fr' ]
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
const doc = {
|
|
236
|
+
_id: '123',
|
|
237
|
+
title: 'Hello World'
|
|
238
|
+
};
|
|
239
|
+
const source = 'en';
|
|
240
|
+
const target = 'es';
|
|
241
|
+
const existing = false;
|
|
242
|
+
|
|
243
|
+
await handler(req, doc, {
|
|
244
|
+
source,
|
|
245
|
+
target,
|
|
246
|
+
existing
|
|
247
|
+
});
|
|
248
|
+
translation.providers = providers;
|
|
249
|
+
apos.notify = savedNotify;
|
|
250
|
+
|
|
251
|
+
assert.equal(doc.title, 'Hello World');
|
|
252
|
+
assert.equal(notifyCallArgs, null);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should skip translation on beforeLocalize if disabled', async function () {
|
|
256
|
+
const translation = apos.modules['@apostrophecms/translation'];
|
|
257
|
+
translation.options.enabled = false;
|
|
258
|
+
const handler = translation.handlers['@apostrophecms/doc-type:beforeLocalize']
|
|
259
|
+
.translate;
|
|
260
|
+
|
|
261
|
+
const savedNotify = apos.notify;
|
|
262
|
+
let notifyCallArgs = null;
|
|
263
|
+
apos.notify = function (...args) {
|
|
264
|
+
notifyCallArgs = args;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
assert(handler);
|
|
268
|
+
|
|
269
|
+
const req = apos.task.getReq({
|
|
270
|
+
query: {
|
|
271
|
+
aposTranslateTargets: [ 'es', 'fr' ]
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
const doc = {
|
|
275
|
+
_id: '123',
|
|
276
|
+
title: 'Hello World'
|
|
277
|
+
};
|
|
278
|
+
const source = 'en';
|
|
279
|
+
const target = 'es';
|
|
280
|
+
const existing = false;
|
|
281
|
+
|
|
282
|
+
await handler(req, doc, {
|
|
283
|
+
source,
|
|
284
|
+
target,
|
|
285
|
+
existing
|
|
286
|
+
});
|
|
287
|
+
translation.options.enabled = true;
|
|
288
|
+
apos.notify = savedNotify;
|
|
289
|
+
|
|
290
|
+
assert.equal(doc.title, 'Hello World');
|
|
291
|
+
assert.equal(notifyCallArgs, null);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should skip translation on beforeLocalize if bad provider', async function () {
|
|
295
|
+
const translation = apos.modules['@apostrophecms/translation'];
|
|
296
|
+
const handler = translation.handlers['@apostrophecms/doc-type:beforeLocalize']
|
|
297
|
+
.translate;
|
|
298
|
+
|
|
299
|
+
const savedNotify = apos.notify;
|
|
300
|
+
let notifyCallArgs = null;
|
|
301
|
+
apos.notify = function (...args) {
|
|
302
|
+
notifyCallArgs = args;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
assert(handler);
|
|
306
|
+
|
|
307
|
+
const req = apos.task.getReq({
|
|
308
|
+
query: {
|
|
309
|
+
aposTranslateTargets: [ 'es', 'fr' ],
|
|
310
|
+
aposTranslateProvider: 'badProvider'
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
const doc = {
|
|
314
|
+
_id: '123',
|
|
315
|
+
title: 'Hello World'
|
|
316
|
+
};
|
|
317
|
+
const source = 'en';
|
|
318
|
+
const target = 'es';
|
|
319
|
+
const existing = false;
|
|
320
|
+
|
|
321
|
+
await handler(req, doc, {
|
|
322
|
+
source,
|
|
323
|
+
target,
|
|
324
|
+
existing
|
|
325
|
+
});
|
|
326
|
+
apos.notify = savedNotify;
|
|
327
|
+
|
|
328
|
+
assert.equal(doc.title, 'Hello World');
|
|
329
|
+
assert.deepEqual(notifyCallArgs, [
|
|
330
|
+
req,
|
|
331
|
+
'Translation provider not found. Page "Hello World" was not translated.',
|
|
332
|
+
{
|
|
333
|
+
type: 'danger',
|
|
334
|
+
dismiss: true
|
|
335
|
+
}
|
|
336
|
+
]);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should skip translation on beforeLocalize if no target match', async function () {
|
|
340
|
+
const translation = apos.modules['@apostrophecms/translation'];
|
|
341
|
+
const handler = translation.handlers['@apostrophecms/doc-type:beforeLocalize']
|
|
342
|
+
.translate;
|
|
343
|
+
const savedNotify = apos.notify;
|
|
344
|
+
let notifyCallArgs = null;
|
|
345
|
+
apos.notify = function (...args) {
|
|
346
|
+
notifyCallArgs = args;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
assert(handler);
|
|
350
|
+
|
|
351
|
+
const req = apos.task.getReq({
|
|
352
|
+
query: {
|
|
353
|
+
aposTranslateTargets: [ 'fr' ],
|
|
354
|
+
aposTranslateProvider: 'fake'
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
const doc = {
|
|
358
|
+
_id: '123',
|
|
359
|
+
title: 'Hello World'
|
|
360
|
+
};
|
|
361
|
+
const source = 'en';
|
|
362
|
+
const target = 'es';
|
|
363
|
+
const options = {};
|
|
364
|
+
|
|
365
|
+
await handler(req, doc, source, target, options);
|
|
366
|
+
apos.notify = savedNotify;
|
|
367
|
+
|
|
368
|
+
assert.equal(doc.title, 'Hello World');
|
|
369
|
+
assert.equal(notifyCallArgs, null);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe('API', function () {
|
|
373
|
+
let jar = null;
|
|
374
|
+
|
|
375
|
+
before(async function () {
|
|
376
|
+
jar = apos.http.jar();
|
|
377
|
+
await t.createAdmin(apos, {
|
|
378
|
+
username: 'admin',
|
|
379
|
+
password: 'admin'
|
|
380
|
+
});
|
|
381
|
+
await t.createUser(apos, 'editor', {
|
|
382
|
+
username: 'editor',
|
|
383
|
+
password: 'editor'
|
|
384
|
+
});
|
|
385
|
+
await t.createUser(apos, 'guest', {
|
|
386
|
+
username: 'guest',
|
|
387
|
+
password: 'guest'
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
async function login(role = 'admin') {
|
|
392
|
+
jar = await t.loginAs(apos, role);
|
|
393
|
+
const page = await apos.http.get('/', {
|
|
394
|
+
jar
|
|
395
|
+
});
|
|
396
|
+
assert(page.match(/logged in/));
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
it('should not get supported languages when not authorized', async function () {
|
|
400
|
+
jar = apos.http.jar();
|
|
401
|
+
await assert.rejects(
|
|
402
|
+
async () => apos.http.get(
|
|
403
|
+
'/api/v1/@apostrophecms/translation/languages',
|
|
404
|
+
{
|
|
405
|
+
jar,
|
|
406
|
+
qs: {
|
|
407
|
+
provider: 'fake',
|
|
408
|
+
source: [ 'en' ],
|
|
409
|
+
target: [ 'es' ]
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
),
|
|
413
|
+
{
|
|
414
|
+
status: 404
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
await login('guest');
|
|
419
|
+
await assert.rejects(
|
|
420
|
+
async () => apos.http.get(
|
|
421
|
+
'/api/v1/@apostrophecms/translation/languages',
|
|
422
|
+
{
|
|
423
|
+
jar,
|
|
424
|
+
qs: {
|
|
425
|
+
provider: 'fake',
|
|
426
|
+
source: [ 'en' ],
|
|
427
|
+
target: [ 'es' ]
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
),
|
|
431
|
+
{
|
|
432
|
+
status: 404
|
|
433
|
+
}
|
|
434
|
+
);
|
|
435
|
+
await t.logout(apos, 'guest', 'guest', jar);
|
|
436
|
+
jar = apos.http.jar();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should get supported languages when authorized', async function () {
|
|
440
|
+
await login('editor');
|
|
441
|
+
|
|
442
|
+
{
|
|
443
|
+
const res = await apos.http.get(
|
|
444
|
+
'/api/v1/@apostrophecms/translation/languages',
|
|
445
|
+
{
|
|
446
|
+
jar,
|
|
447
|
+
qs: {
|
|
448
|
+
// Test without provider to get the first available
|
|
449
|
+
source: [ 'en' ],
|
|
450
|
+
// Don't get confused, `et` stands for
|
|
451
|
+
// Extra- Terrestrial(E.T.)!
|
|
452
|
+
target: [ 'es', 'et' ]
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
assert.deepEqual(res, {
|
|
458
|
+
source: [
|
|
459
|
+
{
|
|
460
|
+
code: 'en',
|
|
461
|
+
supported: true
|
|
462
|
+
}
|
|
463
|
+
],
|
|
464
|
+
target: [
|
|
465
|
+
{
|
|
466
|
+
code: 'es',
|
|
467
|
+
supported: true
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
code: 'et',
|
|
471
|
+
supported: false
|
|
472
|
+
}
|
|
473
|
+
]
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
await t.logout(apos, 'editor', 'editor', jar);
|
|
478
|
+
await login('admin');
|
|
479
|
+
{
|
|
480
|
+
const res = await apos.http.get(
|
|
481
|
+
'/api/v1/@apostrophecms/translation/languages',
|
|
482
|
+
{
|
|
483
|
+
jar,
|
|
484
|
+
qs: {
|
|
485
|
+
provider: 'fake',
|
|
486
|
+
source: [ 'en' ],
|
|
487
|
+
// Don't get confused, `et` stands for
|
|
488
|
+
// Extra- Terrestrial(E.T.)!
|
|
489
|
+
target: [ 'es', 'et' ]
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
assert.deepEqual(res, {
|
|
495
|
+
source: [
|
|
496
|
+
{
|
|
497
|
+
code: 'en',
|
|
498
|
+
supported: true
|
|
499
|
+
}
|
|
500
|
+
],
|
|
501
|
+
target: [
|
|
502
|
+
{
|
|
503
|
+
code: 'es',
|
|
504
|
+
supported: true
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
code: 'et',
|
|
508
|
+
supported: false
|
|
509
|
+
}
|
|
510
|
+
]
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
await t.logout(apos, 'admin', 'admin', jar);
|
|
514
|
+
jar = apos.http.jar();
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it('should validate the provider name', async function () {
|
|
519
|
+
await login('admin');
|
|
520
|
+
await assert.rejects(
|
|
521
|
+
async () => apos.http.get(
|
|
522
|
+
'/api/v1/@apostrophecms/translation/languages',
|
|
523
|
+
{
|
|
524
|
+
jar,
|
|
525
|
+
qs: {
|
|
526
|
+
provider: 'badProvider',
|
|
527
|
+
source: [ 'en' ],
|
|
528
|
+
target: [ 'es' ]
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
),
|
|
532
|
+
{
|
|
533
|
+
status: 400
|
|
534
|
+
}
|
|
535
|
+
);
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
});
|
package/test-lib/util.js
CHANGED
|
@@ -118,6 +118,26 @@ async function loginAs(apos, username, password) {
|
|
|
118
118
|
return jar;
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
+
async function logout(apos, username, password, jar) {
|
|
122
|
+
await apos.http.post(
|
|
123
|
+
'/api/v1/@apostrophecms/login/logout',
|
|
124
|
+
{
|
|
125
|
+
body: {
|
|
126
|
+
username,
|
|
127
|
+
password,
|
|
128
|
+
session: true
|
|
129
|
+
},
|
|
130
|
+
jar
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
await apos.http.get(
|
|
134
|
+
'/',
|
|
135
|
+
{
|
|
136
|
+
jar
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
121
141
|
// Deprecated legacy wrapper for loginAs.
|
|
122
142
|
function getUserJar(apos, { username = 'admin', password } = {}) {
|
|
123
143
|
return loginAs(apos, username, password);
|
|
@@ -129,6 +149,7 @@ module.exports = {
|
|
|
129
149
|
createAdmin,
|
|
130
150
|
createUser,
|
|
131
151
|
loginAs,
|
|
152
|
+
logout,
|
|
132
153
|
getUserJar,
|
|
133
154
|
timeout: (process.env.TEST_TIMEOUT && parseInt(process.env.TEST_TIMEOUT)) || 20000
|
|
134
155
|
};
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// Vue plugin. Create a new directive with i18n support by applying the decorator
|
|
2
|
-
// pattern to VTooltip, then add it to the Vue instance
|
|
3
|
-
|
|
4
|
-
import { VTooltip } from 'v-tooltip';
|
|
5
|
-
|
|
6
|
-
export default {
|
|
7
|
-
install(Vue, options) {
|
|
8
|
-
|
|
9
|
-
const directive = {};
|
|
10
|
-
|
|
11
|
-
Object.assign(VTooltip.options, options);
|
|
12
|
-
let instance;
|
|
13
|
-
|
|
14
|
-
// Right now VTooltip only uses bind, but be forwards-compatible
|
|
15
|
-
extendHandler('bind');
|
|
16
|
-
extendHandler('inserted');
|
|
17
|
-
extendHandler('update');
|
|
18
|
-
extendHandler('componentUpdated');
|
|
19
|
-
extendHandler('unbind');
|
|
20
|
-
|
|
21
|
-
Vue.directive('apos-tooltip', directive);
|
|
22
|
-
|
|
23
|
-
function extendHandler(name) {
|
|
24
|
-
if (VTooltip[name]) {
|
|
25
|
-
directive[name] = (el, binding, vnode, oldVnode) => {
|
|
26
|
-
return VTooltip[name](el, {
|
|
27
|
-
...binding,
|
|
28
|
-
value: localize(binding.value)
|
|
29
|
-
}, vnode, oldVnode);
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function localize(value) {
|
|
35
|
-
if (!value) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
if (!instance) {
|
|
39
|
-
// A headless Vue instance to call $t on. We do this late so we
|
|
40
|
-
// know $t is ready
|
|
41
|
-
instance = new Vue();
|
|
42
|
-
}
|
|
43
|
-
// Something stringable
|
|
44
|
-
if (!value) {
|
|
45
|
-
// VTooltip will be confused if $t converts a falsy value to the string "false"
|
|
46
|
-
return value;
|
|
47
|
-
}
|
|
48
|
-
if (value.content) {
|
|
49
|
-
// Object with a content property
|
|
50
|
-
if (value && value.content) {
|
|
51
|
-
return {
|
|
52
|
-
...value,
|
|
53
|
-
content: (value.localize === false) ? value.content : instance.$t(value.content)
|
|
54
|
-
};
|
|
55
|
-
} else {
|
|
56
|
-
return value;
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
return instance.$t(value);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
defaultOffset: '11',
|
|
3
|
-
defaultPlacement: 'bottom-end',
|
|
4
|
-
defaultTemplate: `
|
|
5
|
-
<div class="apos-tooltip" role="tooltip">
|
|
6
|
-
<div class="apos-tooltip__wrapper">
|
|
7
|
-
<div class="apos-tooltip__arrow"></div>
|
|
8
|
-
<div class="apos-tooltip__inner"></div>
|
|
9
|
-
</div>
|
|
10
|
-
</div>`,
|
|
11
|
-
defaultArrowSelector: '.apos-tooltip__arrow',
|
|
12
|
-
defaultInnerSelector: '.apos-tooltip__inner'
|
|
13
|
-
};
|