зеркало из https://github.com/mozilla/fxa.git
bug(settings): Hash l10n files for cdn deployment
Because: - We need cache busting file names when deploying new content to the CDN This Commit: - Adds grunt-hash job - Configures grunt has to hash ftl files and create mapping files - Adjusts AppLocalizationProvider to use the mapping file to load l10n files
This commit is contained in:
Родитель
5d1652341a
Коммит
914afe5343
|
@ -150,6 +150,7 @@ packages/fxa-react/test/
|
|||
|
||||
# fxa-settings
|
||||
packages/fxa-settings/fxa-content-server-l10n/
|
||||
packages/fxa-settings/public/static
|
||||
packages/fxa-settings/public/locales
|
||||
packages/fxa-settings/legal-docs/
|
||||
packages/fxa-settings/public/legal-docs
|
||||
|
|
|
@ -999,7 +999,7 @@ const conf = (module.exports = convict({
|
|||
},
|
||||
l10n: {
|
||||
baseUrl: {
|
||||
default: '/settings/locales',
|
||||
default: '/settings/static',
|
||||
doc: 'The path (or url) where ftl files are held.',
|
||||
env: 'L10N_BASE_URL',
|
||||
},
|
||||
|
|
|
@ -26,6 +26,17 @@ describe('<AppLocalizationProvider/>', () => {
|
|||
}
|
||||
|
||||
beforeAll(() => {
|
||||
fetchMock.get(
|
||||
'/static-asset-manifest.json',
|
||||
`
|
||||
{
|
||||
"/locales/en-US/greetings.ftl": "/locales/en-US/greetings.ftl",
|
||||
"/locales/en-US/farewells.ftl": "/locales/en-US/farewells.ftl",
|
||||
"/locales/es-ES/greetings.ftl": "/locales/es-ES/greetings.ftl",
|
||||
"/locales/en-GB/greetings.ftl": "/locales/en-GB/greetings.ftl",
|
||||
}
|
||||
`
|
||||
);
|
||||
fetchMock.get('/locales/en-US/greetings.ftl', 'hello = Hello\n');
|
||||
fetchMock.get('/locales/en-US/farewells.ftl', 'goodbye = Goodbye\n');
|
||||
fetchMock.get('/locales/es-ES/greetings.ftl', 'hello = Hola\n');
|
||||
|
|
|
@ -7,9 +7,39 @@ import { LocalizationProvider, ReactLocalization } from '@fluent/react';
|
|||
import React, { Component } from 'react';
|
||||
import { EN_GB_LOCALES, parseAcceptLanguage } from '@fxa/shared/l10n';
|
||||
|
||||
async function fetchMessages(baseDir: string, locale: string, bundle: string) {
|
||||
/**
|
||||
* Gets l10n messages from server
|
||||
* @param baseDir The root location where locales folders are held
|
||||
* @param locale The target language
|
||||
* @param bundle The target bundle (ie main)
|
||||
* @param mappings A set of mappings for static resources.
|
||||
* @returns
|
||||
*/
|
||||
async function fetchMessages(
|
||||
baseDir: string,
|
||||
locale: string,
|
||||
bundle: string,
|
||||
mappings?: Record<string, string>
|
||||
) {
|
||||
try {
|
||||
const response = await fetch(`${baseDir}/${locale}/${bundle}.ftl`);
|
||||
// Build the path to l10n file
|
||||
let path = `locales/${locale}/${bundle}.ftl`;
|
||||
|
||||
// If mappings were proivided see if there is one for the path. This
|
||||
// will be a location where the file path contains a hash in the file
|
||||
// name
|
||||
if (mappings) {
|
||||
path = mappings[path];
|
||||
}
|
||||
|
||||
// If we don't have mapped path, there are no l10n resources for this language.
|
||||
if (!path) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Fetch the file and return the messages
|
||||
const resolvedPath = `${baseDir}/${path}`;
|
||||
const response = await fetch(resolvedPath);
|
||||
const messages = await response.text();
|
||||
|
||||
return messages;
|
||||
|
@ -23,23 +53,41 @@ async function fetchMessages(baseDir: string, locale: string, bundle: string) {
|
|||
function fetchAllMessages(
|
||||
baseDir: string,
|
||||
locale: string,
|
||||
bundles: Array<string>
|
||||
bundles: Array<string>,
|
||||
mappings?: Record<string, string>
|
||||
) {
|
||||
return Promise.all(
|
||||
bundles.map((bndl) => fetchMessages(baseDir, locale, bndl))
|
||||
bundles.map((bndl) => fetchMessages(baseDir, locale, bndl, mappings))
|
||||
);
|
||||
}
|
||||
|
||||
async function fetchL10nHashedMappings(mappingUrl: string) {
|
||||
try {
|
||||
// These mappigns are currently generated with grunt. See grunt task hash-static
|
||||
// in fxa-settings for an example of how the mappings are generated.
|
||||
const mappingsResponse = await fetch(mappingUrl);
|
||||
const json = await mappingsResponse.json();
|
||||
return json;
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function createFluentBundleGenerator(
|
||||
baseDir: string,
|
||||
currentLocales: Array<string>,
|
||||
bundles: Array<string>
|
||||
) {
|
||||
const mappings = await fetchL10nHashedMappings(
|
||||
`${baseDir}/static-asset-manifest.json`
|
||||
);
|
||||
const fetched = await Promise.all(
|
||||
currentLocales
|
||||
.filter((l) => !EN_GB_LOCALES.includes(l))
|
||||
.map(async (locale) => {
|
||||
return { [locale]: await fetchAllMessages(baseDir, locale, bundles) };
|
||||
return {
|
||||
[locale]: await fetchAllMessages(baseDir, locale, bundles, mappings),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -84,7 +132,7 @@ type Props = {
|
|||
|
||||
export default class AppLocalizationProvider extends Component<Props, State> {
|
||||
static defaultProps: Props = {
|
||||
baseDir: '/locales',
|
||||
baseDir: '',
|
||||
userLocales: ['en'],
|
||||
bundles: ['main'],
|
||||
children: React.createElement('div'),
|
||||
|
|
|
@ -37,6 +37,23 @@ module.exports = function (grunt) {
|
|||
dest: 'test/settings.ftl',
|
||||
},
|
||||
},
|
||||
hash: {
|
||||
options: {
|
||||
mapping: 'public/static/static-asset-manifest.json', // The file where the hashed file names will be stored
|
||||
srcBasePath: 'public/', // the base Path you want to remove from the `key` string in the mapping file
|
||||
destBasePath: 'public/static',
|
||||
},
|
||||
locales: {
|
||||
expand: true,
|
||||
cwd: 'public/locales',
|
||||
src: '**/*.ftl',
|
||||
dest: 'public/static/locales/',
|
||||
rename: function (dest, src) {
|
||||
const lang = src.split('/')[0];
|
||||
return `${dest}/${lang}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
ftl: {
|
||||
files: srcPaths,
|
||||
|
@ -51,8 +68,11 @@ module.exports = function (grunt) {
|
|||
grunt.loadNpmTasks('grunt-contrib-copy');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-contrib-concat');
|
||||
grunt.loadNpmTasks('grunt-hash');
|
||||
|
||||
grunt.registerTask('merge-ftl', ['copy:branding-ftl', 'concat:ftl']);
|
||||
grunt.registerTask('merge-ftl:test', ['concat:ftl-test']);
|
||||
grunt.registerTask('watch-ftl', ['watch:ftl']);
|
||||
|
||||
grunt.registerTask('hash-static', ['hash']);
|
||||
};
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
"private": true,
|
||||
"scripts": {
|
||||
"prebuild": "nx l10n-prime && nx legal-prime",
|
||||
"build": "nx build-l10n && nx build-ts && nx build-css && nx build-react",
|
||||
"build": "nx build-l10n && nx build-static && nx build-ts && nx build-css && nx build-react",
|
||||
"build-static": "yarn grunt hash-static",
|
||||
"build-ts": "tsc --build",
|
||||
"build-css": "NODE_ENV=production tailwindcss -i ./src/styles/tailwind.css -o ./src/styles/tailwind.out.css --postcss",
|
||||
"build-storybook": "NODE_ENV=production STORYBOOK_BUILD=1 yarn build-css && NODE_OPTIONS=--openssl-legacy-provider sb build && cp -r public/locales ./storybook-static/locales",
|
||||
|
@ -237,6 +238,7 @@
|
|||
"grunt-cli": "^1.4.3",
|
||||
"grunt-contrib-concat": "^2.1.0",
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"grunt-hash": "^0.5.0",
|
||||
"jest-watch-typeahead": "0.6.5",
|
||||
"mutationobserver-shim": "^0.3.7",
|
||||
"nx": "18.3.1",
|
||||
|
@ -245,6 +247,7 @@
|
|||
"postcss-assets": "^6.0.0",
|
||||
"postcss-import": "^16.1.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sinon": "^15.0.1",
|
||||
"storybook": "^7.0.23",
|
||||
"storybook-addon-rtl": "^0.5.0",
|
||||
|
|
23
yarn.lock
23
yarn.lock
|
@ -41625,6 +41625,7 @@ fsevents@~2.1.1:
|
|||
grunt-cli: ^1.4.3
|
||||
grunt-contrib-concat: ^2.1.0
|
||||
grunt-contrib-watch: ^1.1.0
|
||||
grunt-hash: ^0.5.0
|
||||
html-webpack-plugin: ^5.6.0
|
||||
identity-obj-proxy: ^3.0.0
|
||||
jest: ^29.7.0
|
||||
|
@ -41643,6 +41644,7 @@ fsevents@~2.1.1:
|
|||
postcss-normalize: ^10.0.1
|
||||
postcss-preset-env: ^10.0.5
|
||||
prop-types: ^15.8.1
|
||||
raw-loader: ^4.0.2
|
||||
react-app-polyfill: ^3.0.0
|
||||
react-async-hook: ^4.0.0
|
||||
react-dev-utils: ^12.0.1
|
||||
|
@ -43337,6 +43339,15 @@ fsevents@~2.1.1:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"grunt-hash@npm:^0.5.0":
|
||||
version: 0.5.0
|
||||
resolution: "grunt-hash@npm:0.5.0"
|
||||
bin:
|
||||
grunt-hash: bin/grunt-hash
|
||||
checksum: 2781250dbd476e8302085d816e60a94e0f94f9696e8cd434cecbb66dfc444a561d251409ae88e1f5d82be2996c390aa8681d5fb8f3638ba30e496417e61cf5cd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"grunt-htmllint@npm:0.3.0":
|
||||
version: 0.3.0
|
||||
resolution: "grunt-htmllint@npm:0.3.0"
|
||||
|
@ -59754,6 +59765,18 @@ fsevents@~2.1.1:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"raw-loader@npm:^4.0.2":
|
||||
version: 4.0.2
|
||||
resolution: "raw-loader@npm:4.0.2"
|
||||
dependencies:
|
||||
loader-utils: ^2.0.0
|
||||
schema-utils: ^3.0.0
|
||||
peerDependencies:
|
||||
webpack: ^4.0.0 || ^5.0.0
|
||||
checksum: 51cc1b0d0e8c37c4336b5318f3b2c9c51d6998ad6f56ea09612afcfefc9c1f596341309e934a744ae907177f28efc9f1654eacd62151e82853fcc6d37450e795
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rc@npm:^1.2.7":
|
||||
version: 1.2.8
|
||||
resolution: "rc@npm:1.2.8"
|
||||
|
|
Загрузка…
Ссылка в новой задаче