ghost 4.18.0 → 4.20.1
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.js +9 -8
- package/Gruntfile.js +1 -1
- package/PRIVACY.md +3 -0
- package/content/adapters/README.md +2 -2
- package/core/boot.js +17 -12
- package/core/bridge.js +9 -1
- package/core/built/assets/{chunk.3.b80d3e1e6b8556aaff3c.js → chunk.3.777d43e2ce954ba8b2f5.js} +25 -25
- package/core/built/assets/codemirror/{codemirror-21a09582262987037db73b152fb35f7c.js → codemirror-d25c379b87ec8b33d54ac7149bc0b6ae.js} +14 -14
- package/core/built/assets/ghost-dark-20e2892d4f30d0d1183c9ac725ea37d0.css +1 -0
- package/core/built/assets/{ghost.min-88d647a008a5b1dd678a89ae1e55c038.js → ghost.min-26e427944e719b616b8dc7fbb3bbd2f9.js} +709 -422
- package/core/built/assets/ghost.min-57e46fd3b1145ecf2cbd185a13611f3b.css +1 -0
- package/core/built/assets/icons/arrow-left-small.svg +0 -4
- package/core/built/assets/icons/paintbrush.svg +10 -1
- package/core/built/assets/icons/post.svg +3 -1
- package/core/built/assets/img/footer-marketplace-bg-572b6c6486a7e26316954d599eaa9f30.png +0 -0
- package/core/built/assets/img/marketing/offers-1-f2e1b653c4d5bb90eea9d7a2862530f9.jpg +0 -0
- package/core/built/assets/img/marketing/offers-2-28a225d34cc39d133748431536961d00.jpg +0 -0
- package/core/built/assets/img/marketing/offers-3-2094c91ab21a16c37fbe6ec16c140160.jpg +0 -0
- package/core/built/assets/img/themes/Alto-f4db5af43ca9771c7ac1f754de3ddf2f.png +0 -0
- package/core/built/assets/img/themes/Bulletin-57d45b992ff0e26e0acdce7ed4cccd67.png +0 -0
- package/core/built/assets/img/themes/Casper-c7e784d7188cc5d7f097d9b6c97b0263.jpg +0 -0
- package/core/built/assets/img/themes/Dawn-be81aa8c8caae8fcfb5d5fbec823fdcc.png +0 -0
- package/core/built/assets/img/themes/Digest-d3467ac22a290e1ad3a543014758286e.png +0 -0
- package/core/built/assets/img/themes/Dope-6f8e0bbc199ce4af9a60859e9e6a74ad.png +0 -0
- package/core/built/assets/img/themes/Ease-9c279ea6cec3c0f1823f81c9dd24b116.png +0 -0
- package/core/built/assets/img/themes/Edge-0258906309e11fd075a1d9880aa09b20.png +0 -0
- package/core/built/assets/img/themes/Edition-d8f508e93bc24bdf2716ae6f8b3d44f8.png +0 -0
- package/core/built/assets/img/themes/Editorial-a25a4a34c04dedd858bd5e05ef388b1c.jpg +0 -0
- package/core/built/assets/img/themes/Journal-accf0031bbae0919900a049061e65a04.png +0 -0
- package/core/built/assets/img/themes/London-3f07efcee9e5bfb9a33827064eb77e70.jpg +0 -0
- package/core/built/assets/img/themes/Massively-06edf00108429f7fb8e65f190fba34fe.jpg +0 -0
- package/core/built/assets/img/themes/Ruby-11a53c62015612f4b3aca8f503121225.png +0 -0
- package/core/built/assets/img/themes/Wave-86e8044c2d76cb57a9030e4c24ac9520.png +0 -0
- package/core/built/assets/simplemde/{simplemde-232f69d126310434489071a1891e6d8b.js → simplemde-3ffc0ec9e9fecf29b9a499db678c9e65.js} +14 -14
- package/core/built/assets/{vendor.min-7dc7cf9c92175ebfb9cea95c120ee8a7.js → vendor.min-af502ac4142871500fc424f6a5a254ec.js} +2206 -1859
- package/core/frontend/apps/amp/lib/router.js +1 -1
- package/core/frontend/helpers/match.js +17 -23
- package/core/frontend/meta/author-url.js +1 -1
- package/core/frontend/meta/url.js +1 -1
- package/core/{server → frontend}/public/favicon.ico +0 -0
- package/core/{server → frontend}/public/ghost.css +0 -0
- package/core/{server → frontend}/public/ghost.min.css +0 -0
- package/core/{server → frontend}/public/robots.txt +0 -0
- package/core/{server → frontend}/public/sitemap.xsl +0 -0
- package/core/frontend/services/proxy.js +1 -1
- package/core/frontend/services/rendering.js +1 -1
- package/core/frontend/services/routing/CollectionRouter.js +3 -49
- package/core/frontend/services/routing/ParentRouter.js +1 -4
- package/core/frontend/services/routing/StaticPagesRouter.js +3 -5
- package/core/frontend/services/routing/StaticRoutesRouter.js +4 -6
- package/core/frontend/services/routing/TaxonomyRouter.js +4 -5
- package/core/frontend/services/routing/controllers/collection.js +2 -2
- package/core/frontend/services/routing/controllers/email-post.js +2 -2
- package/core/frontend/services/routing/controllers/entry.js +2 -2
- package/core/frontend/services/routing/controllers/preview.js +2 -2
- package/core/frontend/services/routing/index.js +6 -12
- package/core/frontend/services/routing/registry.js +13 -0
- package/core/frontend/services/routing/router-manager.js +185 -0
- package/core/frontend/services/rss/generate-feed.js +2 -2
- package/core/frontend/services/theme-engine/i18n/i18n.js +267 -28
- package/core/frontend/services/theme-engine/i18n/index.js +1 -1
- package/core/frontend/services/theme-engine/i18n/theme-i18n.js +73 -0
- package/core/frontend/web/index.js +1 -0
- package/core/{server/web/site → frontend/web}/middleware/handle-image-sizes.js +4 -4
- package/core/{server/web/site → frontend/web}/middleware/index.js +0 -0
- package/core/{server/web/site → frontend/web}/middleware/redirect-ghost-to-admin.js +3 -3
- package/core/{server/web/site → frontend/web}/middleware/serve-favicon.js +6 -6
- package/core/{server/web/site → frontend/web}/middleware/serve-public-file.js +2 -2
- package/core/{server/web/site → frontend/web}/middleware/static-theme.js +3 -3
- package/core/frontend/web/routes.js +13 -0
- package/core/{server/web/site/app.js → frontend/web/site.js} +12 -16
- package/core/server/adapters/storage/LocalFileStorage.js +35 -39
- package/core/server/adapters/storage/index.js +12 -2
- package/core/server/api/canary/custom-theme-settings.js +2 -2
- package/core/server/api/canary/images.js +1 -1
- package/core/server/api/canary/oembed.js +2 -2
- package/core/server/api/canary/offers.js +29 -1
- package/core/server/api/canary/posts-public.js +6 -2
- package/core/server/api/canary/products.js +6 -2
- package/core/server/api/canary/tags-public.js +6 -2
- package/core/server/api/canary/users.js +9 -4
- package/core/server/api/canary/utils/serializers/output/custom-theme-settings.js +2 -2
- package/core/server/api/canary/utils/serializers/output/notifications.js +1 -0
- package/core/server/api/canary/utils/serializers/output/settings.js +2 -3
- package/core/server/api/canary/utils/serializers/output/utils/url.js +1 -1
- package/core/server/api/canary/utils/validators/input/oembed.js +4 -1
- package/core/server/api/canary/utils/validators/input/passwordreset.js +8 -3
- package/core/server/api/canary/utils/validators/input/settings.js +5 -4
- package/core/server/api/canary/utils/validators/input/setup.js +6 -2
- package/core/server/api/canary/utils/validators/input/users.js +6 -2
- package/core/server/api/canary/utils/validators/input/webhooks.js +8 -3
- package/core/server/api/v2/images.js +1 -1
- package/core/server/api/v2/utils/serializers/output/authentication.js +9 -4
- package/core/server/api/v2/utils/serializers/output/notifications.js +1 -0
- package/core/server/api/v2/utils/serializers/output/users.js +5 -3
- package/core/server/api/v2/utils/serializers/output/utils/url.js +1 -1
- package/core/server/api/v2/utils/validators/input/images.js +11 -6
- package/core/server/api/v2/utils/validators/input/invitations.js +14 -6
- package/core/server/api/v2/utils/validators/input/invites.js +6 -2
- package/core/server/api/v2/utils/validators/input/oembed.js +6 -2
- package/core/server/api/v2/utils/validators/input/passwordreset.js +8 -3
- package/core/server/api/v2/utils/validators/input/settings.js +10 -4
- package/core/server/api/v2/utils/validators/input/setup.js +6 -2
- package/core/server/api/v2/utils/validators/input/users.js +5 -2
- package/core/server/api/v3/authentication.js +6 -2
- package/core/server/api/v3/authors-public.js +6 -2
- package/core/server/api/v3/email.js +9 -4
- package/core/server/api/v3/images.js +1 -1
- package/core/server/api/v3/integrations.js +7 -3
- package/core/server/api/v3/invites.js +6 -3
- package/core/server/api/v3/labels.js +10 -5
- package/core/server/api/v3/memberSigninUrls.js +5 -2
- package/core/server/api/v3/oembed.js +2 -2
- package/core/server/api/v3/pages-public.js +5 -2
- package/core/server/api/v3/pages.js +6 -3
- package/core/server/api/v3/posts-public.js +5 -3
- package/core/server/api/v3/posts.js +7 -3
- package/core/server/api/v3/preview.js +5 -3
- package/core/server/api/v3/session.js +7 -3
- package/core/server/api/v3/settings.js +8 -3
- package/core/server/api/v3/slugs.js +5 -4
- package/core/server/api/v3/utils/serializers/output/authentication.js +10 -4
- package/core/server/api/v3/utils/serializers/output/notifications.js +1 -0
- package/core/server/api/v3/utils/serializers/output/settings.js +2 -3
- package/core/server/api/v3/utils/serializers/output/users.js +6 -2
- package/core/server/api/v3/utils/serializers/output/utils/url.js +1 -1
- package/core/server/api/v3/utils/validators/input/images.js +12 -7
- package/core/server/api/v3/utils/validators/input/invitations.js +14 -6
- package/core/server/api/v3/utils/validators/input/invites.js +6 -2
- package/core/server/api/v3/utils/validators/input/oembed.js +6 -2
- package/core/server/api/v3/utils/validators/input/passwordreset.js +8 -3
- package/core/server/api/v3/utils/validators/input/settings.js +5 -4
- package/core/server/api/v3/utils/validators/input/setup.js +6 -2
- package/core/server/api/v3/utils/validators/input/users.js +6 -2
- package/core/server/api/v3/utils/validators/input/webhooks.js +8 -3
- package/core/server/data/exporter/table-lists.js +2 -1
- package/core/server/data/importer/handlers/image.js +1 -1
- package/core/server/data/importer/importers/image.js +1 -1
- package/core/server/data/migrations/init/1-create-tables.js +7 -8
- package/core/server/data/migrations/init/2-create-fixtures.js +8 -8
- package/core/server/data/migrations/versions/4.19/01-add-active-column-to-offers.js +7 -0
- package/core/server/data/migrations/versions/4.19/02-add-offer-redemptions-table.js +8 -0
- package/core/server/data/migrations/versions/4.20/01-remove-offer-redemptions-table.js +19 -0
- package/core/server/data/migrations/versions/4.20/02-remove-offers-table.js +30 -0
- package/core/server/data/migrations/versions/4.20/03-add-offers-table.js +21 -0
- package/core/server/data/migrations/versions/4.20/04-add-offer-redemptions-table.js +9 -0
- package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +41 -0
- package/core/server/data/schema/fixtures/utils.js +150 -143
- package/core/server/data/schema/schema.js +15 -3
- package/core/server/frontend/ghost.min.css +1 -0
- package/core/server/lib/image/blog-icon.js +10 -10
- package/core/server/lib/image/image-size.js +5 -5
- package/core/server/lib/image/image-utils.js +4 -4
- package/core/server/lib/image/index.js +1 -2
- package/core/server/lib/mobiledoc.js +3 -2
- package/core/server/models/action.js +7 -4
- package/core/server/models/base/plugins/overrides.js +19 -6
- package/core/server/models/custom-theme-setting.js +56 -1
- package/core/server/models/index.js +4 -45
- package/core/server/models/member.js +5 -0
- package/core/server/models/offer-redemption.js +10 -0
- package/core/server/models/user.js +2 -1
- package/core/server/overrides.js +6 -2
- package/core/server/run-update-check.js +0 -3
- package/core/server/services/adapter-manager/config.js +1 -0
- package/core/server/services/adapter-manager/index.js +9 -5
- package/core/server/services/adapter-manager/options-resolver.js +18 -0
- package/core/server/services/bulk-email/bulk-email-processor.js +6 -2
- package/core/server/services/bulk-email/mailgun.js +1 -1
- package/core/server/services/custom-theme-settings.js +10 -4
- package/core/server/services/invites/index.js +0 -2
- package/core/server/services/invites/invites.js +5 -5
- package/core/server/services/mail/GhostMailer.js +18 -10
- package/core/server/services/mega/mega.js +3 -3
- package/core/server/services/mega/post-email-serializer.js +2 -2
- package/core/server/services/members/api.js +3 -4
- package/core/server/services/members/emails/signin.js +1 -1
- package/core/server/services/members/emails/signup.js +1 -1
- package/core/server/services/members/emails/subscribe.js +1 -1
- package/core/server/services/members/middleware.js +10 -0
- package/core/server/services/members/service.js +2 -1
- package/core/server/services/notifications/index.js +1 -1
- package/core/server/services/notifications/notifications.js +40 -35
- package/core/server/services/oembed.js +4 -9
- package/core/server/services/offers/service.js +16 -6
- package/core/server/services/permissions/public.js +6 -2
- package/core/server/services/route-settings/route-settings.js +1 -1
- package/core/server/services/settings/index.js +3 -1
- package/core/server/services/settings/settings-bread-service.js +42 -20
- package/core/server/services/slack.js +1 -1
- package/core/server/services/themes/activate.js +2 -2
- package/core/server/services/themes/activation-bridge.js +6 -6
- package/core/server/services/themes/storage.js +1 -1
- package/core/{frontend → server}/services/url/Queue.js +0 -0
- package/core/{frontend → server}/services/url/Resource.js +0 -0
- package/core/{frontend → server}/services/url/Resources.js +2 -2
- package/core/{frontend → server}/services/url/UrlGenerator.js +14 -14
- package/core/{frontend → server}/services/url/UrlService.js +12 -15
- package/core/{frontend → server}/services/url/Urls.js +1 -1
- package/core/{frontend → server}/services/url/configs/canary.js +0 -0
- package/core/{frontend → server}/services/url/configs/v2.js +0 -0
- package/core/{frontend → server}/services/url/configs/v3.js +0 -0
- package/core/{frontend → server}/services/url/configs/v4.js +0 -0
- package/core/{frontend → server}/services/url/index.js +0 -0
- package/core/server/services/xmlrpc.js +1 -1
- package/core/server/update-check.js +3 -3
- package/core/server/web/admin/controller.js +11 -0
- package/core/server/web/admin/views/default-prod.html +4 -4
- package/core/server/web/admin/views/default.html +4 -4
- package/core/server/web/api/app.js +8 -9
- package/core/server/web/members/app.js +1 -0
- package/core/server/web/oauth/app.js +4 -2
- package/core/server/web/parent/backend.js +3 -3
- package/core/server/web/parent/frontend.js +2 -2
- package/core/server/web/shared/middlewares/api/spam-prevention.js +0 -2
- package/core/server/web/shared/middlewares/custom-redirects.js +0 -8
- package/core/server/web/shared/middlewares/maintenance.js +1 -1
- package/core/server/web/well-known.js +10 -10
- package/core/shared/config/defaults.json +2 -2
- package/core/shared/config/overrides.json +1 -1
- package/core/shared/express.js +10 -0
- package/core/shared/html-to-plaintext.js +2 -2
- package/core/shared/labs.js +14 -6
- package/loggingrc.js +10 -0
- package/package.json +60 -57
- package/yarn.lock +1317 -914
- package/core/built/assets/ghost-dark-13627f10941a7dbb2b12e1d41dc51c34.css +0 -1
- package/core/built/assets/ghost.min-d9cbfb4eb2db8915fcd2bf2416218616.css +0 -1
- package/core/built/assets/img/themes/London-68501c8ab797de7f2851cf9ea0a28e26.jpg +0 -0
- package/core/frontend/services/routing/bootstrap.js +0 -114
- package/core/server/public/404-ghost.png +0 -0
- package/core/server/public/404-ghost@2x.png +0 -0
- package/core/server/web/site/index.js +0 -1
- package/core/server/web/site/routes.js +0 -9
- package/core/shared/i18n/i18n.js +0 -312
- package/core/shared/i18n/index.js +0 -6
- package/core/shared/i18n/translations/en.json +0 -675
|
Binary file
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
const debug = require('@tryghost/debug')('routing');
|
|
2
|
-
const _ = require('lodash');
|
|
3
|
-
const StaticRoutesRouter = require('./StaticRoutesRouter');
|
|
4
|
-
const StaticPagesRouter = require('./StaticPagesRouter');
|
|
5
|
-
const CollectionRouter = require('./CollectionRouter');
|
|
6
|
-
const TaxonomyRouter = require('./TaxonomyRouter');
|
|
7
|
-
const PreviewRouter = require('./PreviewRouter');
|
|
8
|
-
const ParentRouter = require('./ParentRouter');
|
|
9
|
-
const EmailRouter = require('./EmailRouter');
|
|
10
|
-
const UnsubscribeRouter = require('./UnsubscribeRouter');
|
|
11
|
-
|
|
12
|
-
// This emits its own routing events
|
|
13
|
-
const events = require('../../../server/lib/common/events');
|
|
14
|
-
|
|
15
|
-
const defaultApiVersion = 'v4';
|
|
16
|
-
|
|
17
|
-
const registry = require('./registry');
|
|
18
|
-
let siteRouter;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @description The `init` function will return the wrapped parent express router and will start creating all
|
|
22
|
-
* routers if you pass the option "start: true".
|
|
23
|
-
*
|
|
24
|
-
* CASES:
|
|
25
|
-
* - if Ghost starts, it will first init the site app with the wrapper router and then call `start`
|
|
26
|
-
* separately, because it could be that your blog goes into maintenance mode
|
|
27
|
-
* - if you change your route settings, we will re-initialise routing
|
|
28
|
-
*
|
|
29
|
-
* @param {Object} options
|
|
30
|
-
* @returns {ExpressRouter}
|
|
31
|
-
*/
|
|
32
|
-
module.exports.init = ({start = false, routerSettings, apiVersion}) => {
|
|
33
|
-
debug('bootstrap init', start, apiVersion, routerSettings);
|
|
34
|
-
|
|
35
|
-
registry.resetAllRouters();
|
|
36
|
-
registry.resetAllRoutes();
|
|
37
|
-
|
|
38
|
-
events.emit('routers.reset');
|
|
39
|
-
|
|
40
|
-
siteRouter = new ParentRouter('SiteRouter');
|
|
41
|
-
registry.setRouter('siteRouter', siteRouter);
|
|
42
|
-
|
|
43
|
-
if (start) {
|
|
44
|
-
apiVersion = apiVersion || defaultApiVersion;
|
|
45
|
-
this.start(apiVersion, routerSettings);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return siteRouter.router();
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* @description This function will create the routers based on the route settings
|
|
53
|
-
*
|
|
54
|
-
* The routers are created in a specific order. This order defines who can get a resource first or
|
|
55
|
-
* who can dominant other routers.
|
|
56
|
-
*
|
|
57
|
-
* 1. Preview + Unsubscribe Routers: Strongest inbuilt features, which you can never override.
|
|
58
|
-
* 2. Static Routes: Very strong, because you can override any urls and redirect to a static route.
|
|
59
|
-
* 3. Taxonomies: Stronger than collections, because it's an inbuilt feature.
|
|
60
|
-
* 4. Collections
|
|
61
|
-
* 5. Static Pages: Weaker than collections, because we first try to find a post slug and fallback to lookup a static page.
|
|
62
|
-
* 6. Internal Apps: Weakest
|
|
63
|
-
*
|
|
64
|
-
* @param {string} apiVersion
|
|
65
|
-
* @param {object} routerSettings
|
|
66
|
-
*/
|
|
67
|
-
module.exports.start = (apiVersion, routerSettings) => {
|
|
68
|
-
debug('bootstrap start', apiVersion, routerSettings);
|
|
69
|
-
const RESOURCE_CONFIG = require(`./config/${apiVersion}`);
|
|
70
|
-
|
|
71
|
-
const unsubscribeRouter = new UnsubscribeRouter();
|
|
72
|
-
siteRouter.mountRouter(unsubscribeRouter.router());
|
|
73
|
-
registry.setRouter('unsubscribeRouter', unsubscribeRouter);
|
|
74
|
-
|
|
75
|
-
const emailRouter = new EmailRouter(RESOURCE_CONFIG);
|
|
76
|
-
siteRouter.mountRouter(emailRouter.router());
|
|
77
|
-
registry.setRouter('emailRouter', emailRouter);
|
|
78
|
-
|
|
79
|
-
const previewRouter = new PreviewRouter(RESOURCE_CONFIG);
|
|
80
|
-
siteRouter.mountRouter(previewRouter.router());
|
|
81
|
-
registry.setRouter('previewRouter', previewRouter);
|
|
82
|
-
|
|
83
|
-
_.each(routerSettings.routes, (value, key) => {
|
|
84
|
-
const staticRoutesRouter = new StaticRoutesRouter(key, value);
|
|
85
|
-
siteRouter.mountRouter(staticRoutesRouter.router());
|
|
86
|
-
|
|
87
|
-
registry.setRouter(staticRoutesRouter.identifier, staticRoutesRouter);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
_.each(routerSettings.collections, (value, key) => {
|
|
91
|
-
const collectionRouter = new CollectionRouter(key, value, RESOURCE_CONFIG);
|
|
92
|
-
siteRouter.mountRouter(collectionRouter.router());
|
|
93
|
-
registry.setRouter(collectionRouter.identifier, collectionRouter);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const staticPagesRouter = new StaticPagesRouter(RESOURCE_CONFIG);
|
|
97
|
-
siteRouter.mountRouter(staticPagesRouter.router());
|
|
98
|
-
|
|
99
|
-
registry.setRouter('staticPagesRouter', staticPagesRouter);
|
|
100
|
-
|
|
101
|
-
_.each(routerSettings.taxonomies, (value, key) => {
|
|
102
|
-
const taxonomyRouter = new TaxonomyRouter(key, value, RESOURCE_CONFIG);
|
|
103
|
-
siteRouter.mountRouter(taxonomyRouter.router());
|
|
104
|
-
|
|
105
|
-
registry.setRouter(taxonomyRouter.identifier, taxonomyRouter);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const appRouter = new ParentRouter('AppsRouter');
|
|
109
|
-
siteRouter.mountRouter(appRouter.router());
|
|
110
|
-
|
|
111
|
-
registry.setRouter('appRouter', appRouter);
|
|
112
|
-
|
|
113
|
-
debug('Routes:', registry.getAllRoutes());
|
|
114
|
-
};
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = require('./app');
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
const debug = require('@tryghost/debug')('routing');
|
|
2
|
-
const routing = require('../../../frontend/services/routing');
|
|
3
|
-
const routeSettings = require('../../services/route-settings');
|
|
4
|
-
|
|
5
|
-
module.exports = function siteRoutes(options = {}) {
|
|
6
|
-
debug('site Routes', options);
|
|
7
|
-
options.routerSettings = routeSettings.loadRouteSettingsSync();
|
|
8
|
-
return routing.bootstrap.init(options);
|
|
9
|
-
};
|
package/core/shared/i18n/i18n.js
DELETED
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
const errors = require('@tryghost/errors');
|
|
2
|
-
const fs = require('fs-extra');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const MessageFormat = require('intl-messageformat');
|
|
5
|
-
const jp = require('jsonpath');
|
|
6
|
-
const isString = require('lodash/isString');
|
|
7
|
-
const isObject = require('lodash/isObject');
|
|
8
|
-
const isEqual = require('lodash/isEqual');
|
|
9
|
-
const isNil = require('lodash/isNil');
|
|
10
|
-
const merge = require('lodash/merge');
|
|
11
|
-
const get = require('lodash/get');
|
|
12
|
-
|
|
13
|
-
class I18n {
|
|
14
|
-
/**
|
|
15
|
-
* @param {objec} [options]
|
|
16
|
-
* @param {string} basePath - the base path to the translations directory
|
|
17
|
-
* @param {string} [locale] - a locale string
|
|
18
|
-
* @param {{dot|fulltext}} [stringMode] - which mode our translation keys use
|
|
19
|
-
* @param {{object}} [logging] - logging method
|
|
20
|
-
*/
|
|
21
|
-
constructor(options = {}) {
|
|
22
|
-
this._basePath = options.basePath || __dirname;
|
|
23
|
-
this._locale = options.locale || this.defaultLocale();
|
|
24
|
-
this._stringMode = options.stringMode || 'dot';
|
|
25
|
-
this._logging = options.logging || console;
|
|
26
|
-
|
|
27
|
-
this._strings = null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* BasePath getter & setter used for testing
|
|
32
|
-
*/
|
|
33
|
-
set basePath(basePath) {
|
|
34
|
-
this._basePath = basePath;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Need to call init after this
|
|
39
|
-
*/
|
|
40
|
-
get basePath() {
|
|
41
|
-
return this._basePath;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* English is our default locale
|
|
46
|
-
*/
|
|
47
|
-
defaultLocale() {
|
|
48
|
-
return 'en';
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
supportedLocales() {
|
|
52
|
-
return [this.defaultLocale()];
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Exporting the current locale (e.g. "en") to make it available for other files as well,
|
|
57
|
-
* such as core/frontend/helpers/date.js and core/frontend/helpers/lang.js
|
|
58
|
-
*/
|
|
59
|
-
locale() {
|
|
60
|
-
return this._locale;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Helper method to find and compile the given data context with a proper string resource.
|
|
65
|
-
*
|
|
66
|
-
* @param {string} translationPath Path within the JSON language file to desired string (ie: "errors.init.jsNotBuilt")
|
|
67
|
-
* @param {object} [bindings]
|
|
68
|
-
* @returns {string}
|
|
69
|
-
*/
|
|
70
|
-
t(translationPath, bindings) {
|
|
71
|
-
let string;
|
|
72
|
-
let msg;
|
|
73
|
-
|
|
74
|
-
string = this._findString(translationPath);
|
|
75
|
-
|
|
76
|
-
// If the path returns an array (as in the case with anything that has multiple paragraphs such as emails), then
|
|
77
|
-
// loop through them and return an array of translated/formatted strings. Otherwise, just return the normal
|
|
78
|
-
// translated/formatted string.
|
|
79
|
-
if (Array.isArray(string)) {
|
|
80
|
-
msg = [];
|
|
81
|
-
string.forEach(function (s) {
|
|
82
|
-
msg.push(this._formatMessage(s, bindings));
|
|
83
|
-
});
|
|
84
|
-
} else {
|
|
85
|
-
msg = this._formatMessage(string, bindings);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return msg;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Setup i18n support:
|
|
93
|
-
* - Load proper language file into memory
|
|
94
|
-
*/
|
|
95
|
-
init() {
|
|
96
|
-
this._strings = this._loadStrings();
|
|
97
|
-
|
|
98
|
-
this._initializeIntl();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Attempt to load strings from a file
|
|
103
|
-
*
|
|
104
|
-
* @param {sting} [locale]
|
|
105
|
-
* @returns {object} strings
|
|
106
|
-
*/
|
|
107
|
-
_loadStrings(locale) {
|
|
108
|
-
locale = locale || this.locale();
|
|
109
|
-
|
|
110
|
-
try {
|
|
111
|
-
return this._readTranslationsFile(locale);
|
|
112
|
-
} catch (err) {
|
|
113
|
-
if (err.code === 'ENOENT') {
|
|
114
|
-
this._handleMissingFileError(locale, err);
|
|
115
|
-
|
|
116
|
-
if (locale !== this.defaultLocale()) {
|
|
117
|
-
this._handleFallbackToDefault();
|
|
118
|
-
return this._loadStrings(this.defaultLocale());
|
|
119
|
-
}
|
|
120
|
-
} else if (err instanceof SyntaxError) {
|
|
121
|
-
this._handleInvalidFileError(locale, err);
|
|
122
|
-
} else {
|
|
123
|
-
throw err;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// At this point we've done all we can and strings must be an object
|
|
127
|
-
return {};
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Do the lookup within the JSON file using jsonpath
|
|
133
|
-
*
|
|
134
|
-
* @param {String} msgPath
|
|
135
|
-
*/
|
|
136
|
-
_getCandidateString(msgPath) {
|
|
137
|
-
// Our default string mode is "dot" for dot-notation, e.g. $.something.like.this used in the backend
|
|
138
|
-
// Both jsonpath's dot-notation and bracket-notation start with '$' E.g.: $.store.book.title or $['store']['book']['title']
|
|
139
|
-
// While bracket-notation allows any Unicode characters in keys (i.e. for themes / fulltext mode) E.g. $['Read more']
|
|
140
|
-
// dot-notation allows only word characters in keys for backend messages (that is \w or [A-Za-z0-9_] in RegExp)
|
|
141
|
-
let jsonPath = `$.${msgPath}`;
|
|
142
|
-
let fallback = null;
|
|
143
|
-
|
|
144
|
-
if (this._stringMode === 'fulltext') {
|
|
145
|
-
jsonPath = jp.stringify(['$', msgPath]);
|
|
146
|
-
// In fulltext mode we can use the passed string as a fallback
|
|
147
|
-
fallback = msgPath;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
return jp.value(this._strings, jsonPath) || fallback;
|
|
152
|
-
} catch (err) {
|
|
153
|
-
this._handleInvalidKeyError(msgPath, err);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Parse JSON file for matching locale, returns string giving path.
|
|
159
|
-
*
|
|
160
|
-
* @param {string} msgPath Path with in the JSON language file to desired string (ie: "errors.init.jsNotBuilt")
|
|
161
|
-
* @returns {string}
|
|
162
|
-
*/
|
|
163
|
-
_findString(msgPath, opts) {
|
|
164
|
-
const options = merge({log: true}, opts || {});
|
|
165
|
-
let candidateString;
|
|
166
|
-
let matchingString;
|
|
167
|
-
|
|
168
|
-
// no path? no string
|
|
169
|
-
if (msgPath.length === 0 || !isString(msgPath)) {
|
|
170
|
-
this._handleEmptyKeyError();
|
|
171
|
-
return '';
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// If not in memory, load translations for core
|
|
175
|
-
if (isNil(this._strings)) {
|
|
176
|
-
this._handleUninitialisedError(msgPath);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
candidateString = this._getCandidateString(msgPath);
|
|
180
|
-
|
|
181
|
-
matchingString = candidateString || {};
|
|
182
|
-
|
|
183
|
-
if (isObject(matchingString) || isEqual(matchingString, {})) {
|
|
184
|
-
if (options.log) {
|
|
185
|
-
this._handleMissingKeyError(msgPath);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
matchingString = this._fallbackError();
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return matchingString;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
_translationFileDirs() {
|
|
195
|
-
return [this.basePath];
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// If we are passed a locale, use that, else use this.locale
|
|
199
|
-
_translationFileName(locale) {
|
|
200
|
-
return `${locale || this.locale()}.json`;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Read the translations file
|
|
205
|
-
* Error handling to be done by consumer
|
|
206
|
-
*
|
|
207
|
-
* @param {string} locale
|
|
208
|
-
*/
|
|
209
|
-
_readTranslationsFile(locale) {
|
|
210
|
-
const filePath = path.join(...this._translationFileDirs(), this._translationFileName(locale));
|
|
211
|
-
const content = fs.readFileSync(filePath);
|
|
212
|
-
return JSON.parse(content);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Format the string using the correct locale and applying any bindings
|
|
217
|
-
* @param {String} string
|
|
218
|
-
* @param {Object} bindings
|
|
219
|
-
*/
|
|
220
|
-
_formatMessage(string, bindings) {
|
|
221
|
-
let currentLocale = this.locale();
|
|
222
|
-
let msg = new MessageFormat(string, currentLocale);
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
msg = msg.format(bindings);
|
|
226
|
-
} catch (err) {
|
|
227
|
-
this._handleFormatError(err);
|
|
228
|
-
|
|
229
|
-
// fallback
|
|
230
|
-
msg = new MessageFormat(this._fallbackError(), currentLocale);
|
|
231
|
-
msg = msg.format();
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return msg;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* [Private] Setup i18n support:
|
|
239
|
-
* - Polyfill node.js if it does not have Intl support or support for a particular locale
|
|
240
|
-
*/
|
|
241
|
-
_initializeIntl() {
|
|
242
|
-
let hasBuiltInLocaleData;
|
|
243
|
-
let IntlPolyfill;
|
|
244
|
-
|
|
245
|
-
if (global.Intl) {
|
|
246
|
-
// Determine if the built-in `Intl` has the locale data we need.
|
|
247
|
-
hasBuiltInLocaleData = this.supportedLocales().every(function (locale) {
|
|
248
|
-
return Intl.NumberFormat.supportedLocalesOf(locale)[0] === locale &&
|
|
249
|
-
Intl.DateTimeFormat.supportedLocalesOf(locale)[0] === locale;
|
|
250
|
-
});
|
|
251
|
-
if (!hasBuiltInLocaleData) {
|
|
252
|
-
// `Intl` exists, but it doesn't have the data we need, so load the
|
|
253
|
-
// polyfill and replace the constructors with need with the polyfill's.
|
|
254
|
-
IntlPolyfill = require('intl');
|
|
255
|
-
Intl.NumberFormat = IntlPolyfill.NumberFormat;
|
|
256
|
-
Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
|
|
257
|
-
}
|
|
258
|
-
} else {
|
|
259
|
-
// No `Intl`, so use and load the polyfill.
|
|
260
|
-
global.Intl = require('intl');
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
_handleUninitialisedError(key) {
|
|
265
|
-
this._logging.warn(`i18n was used before it was initialised with key ${key}`);
|
|
266
|
-
this.init();
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
_handleFormatError(err) {
|
|
270
|
-
this._logging.error(err.message);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
_handleFallbackToDefault() {
|
|
274
|
-
this._logging.warn(`i18n is falling back to ${this.defaultLocale()}.json.`);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
_handleMissingFileError(locale) {
|
|
278
|
-
this._logging.warn(`i18n was unable to find ${locale}.json.`);
|
|
279
|
-
}
|
|
280
|
-
_handleInvalidFileError(locale, err) {
|
|
281
|
-
this._logging.error(new errors.IncorrectUsageError({
|
|
282
|
-
err,
|
|
283
|
-
message: `i18n was unable to parse ${locale}.json. Please check that it is valid JSON.`
|
|
284
|
-
}));
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
_handleEmptyKeyError() {
|
|
288
|
-
this._logging.warn('i18n.t() was called without a key');
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
_handleMissingKeyError(key) {
|
|
292
|
-
this._logging.error(new errors.IncorrectUsageError({
|
|
293
|
-
message: `i18n.t() was called with a key that could not be found: ${key}`
|
|
294
|
-
}));
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
_handleInvalidKeyError(key, err) {
|
|
298
|
-
throw new errors.IncorrectUsageError({
|
|
299
|
-
err,
|
|
300
|
-
message: `i18n.t() called with an invalid key: ${key}`
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* A really basic error for if everything goes wrong
|
|
306
|
-
*/
|
|
307
|
-
_fallbackError() {
|
|
308
|
-
return get(this._strings, 'errors.errors.anErrorOccurred', 'An error occurred');
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
module.exports = I18n;
|