Skip to content

Commit

Permalink
Add pontoon strings & build step to transform into webextension strings
Browse files Browse the repository at this point in the history
- Add pontoon-to-webext.js script from bwinton/SnoozeTabs repo and
  related npm dependencies (to be removed when/if that script is
  published on npm as a standalone module).

- Add extracted strings to a properties file at the location expected by
  Pontoon.

- Add a pontoon-to-webextension build step to the Makefile

- Also, replace the hand made messages.json with the more nicely-formatted
  script output.

This commit, together with the fix for mozilla-services#2344, closes mozilla-services#2294.
  • Loading branch information
jaredhirsch committed Mar 11, 2017
1 parent 89de1a2 commit 7d3e6ef
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 27 deletions.
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ imgs_server_dest := $(imgs_source:%=build/server/%)

raven_source := $(shell node -e 'console.log(require.resolve("raven-js/dist/raven.js"))')

l10n_source := $(wildcard locales/*)
l10n_dest := $(l10n_source:%/webextension.properties=addon/webextension/_locales/%/messages.json)

## General transforms:
# These cover standard ways of building files given a source

Expand Down Expand Up @@ -87,7 +90,7 @@ build/%.html: %.html
cp $< $@

.PHONY: addon
addon: npm set_backend set_sentry addon/webextension/manifest.json addon/webextension/build/shot.js addon/webextension/build/inlineSelectionCss.js addon/webextension/build/raven.js addon/webextension/build/defaultSentryDsn.js
addon: npm set_backend set_sentry addon/webextension/manifest.json addon_locales addon/webextension/build/shot.js addon/webextension/build/inlineSelectionCss.js addon/webextension/build/raven.js addon/webextension/build/defaultSentryDsn.js

.PHONY: zip
zip: addon
Expand All @@ -103,6 +106,9 @@ signed_xpi: addon
./node_modules/.bin/web-ext sign --api-key=${AMO_USER} --api-secret=${AMO_SECRET} --source-dir addon/webextension/
mv web-ext-artifacts/*.xpi build/pageshot.xpi

addon_locales: $(l10n_dest)
./bin/pontoon-to-webext.js --dest addon/webextension/_locales

addon/webextension/manifest.json: addon/webextension/manifest.json.template build/.backend.txt package.json
./bin/build-scripts/update_manifest $< $@

Expand Down
98 changes: 72 additions & 26 deletions addon/webextension/_locales/en_US/messages.json
Original file line number Diff line number Diff line change
@@ -1,33 +1,79 @@
{
"addonDescription": { "message": "Page Shot takes clips and screenshots from pages, and can save a permanent copy of a page." },
"addonAuthorsList": { "message": "Ian Bicking, Donovan Preston, and Bram Pitoyo" },
"toolbarButtonLabel": { "message": "Take a shot" },
"contextMenuLabel": { "message": "Create Page Shot" },
"myShotsLink": { "message": "My Shots" },
"screenshotInstructions": { "message": "Drag or click on the page to select a region. Press ESC to cancel." },
"saveScreenshotSelectedArea": { "message": "Save" },
"saveScreenshotVisibleArea": { "message": "Save visible" },
"saveScreenshotFullPage": { "message": "Save full page" },
"cancelScreenshot": { "message": "Cancel" },
"downloadScreenshot": { "message": "Download" },
"notificationLinkCopiedTitle": { "message": "Link Copied" },
"addonDescription": {
"message": "Page Shot takes clips and screenshots from pages, and can save a permanent copy of a page."
},
"addonAuthorsList": {
"message": "Ian Bicking, Donovan Preston, and Bram Pitoyo"
},
"toolbarButtonLabel": {
"message": "Take a shot"
},
"contextMenuLabel": {
"message": "Create Page Shot"
},
"myShotsLink": {
"message": "My Shots"
},
"screenshotInstructions": {
"message": "Drag or click on the page to select a region. Press ESC to cancel."
},
"saveScreenshotSelectedArea": {
"message": "Save"
},
"saveScreenshotVisibleArea": {
"message": "Save visible"
},
"saveScreenshotFullPage": {
"message": "Save full page"
},
"cancelScreenshot": {
"message": "Cancel"
},
"downloadScreenshot": {
"message": "Download"
},
"notificationLinkCopiedTitle": {
"message": "Link Copied"
},
"notificationLinkCopiedDetails": {
"message": "The link to your shot has been copied to the clipboard. Press $META_KEY$-V to paste.",
"placeholders": {
"META_KEY": {
"meta_key": {
"content": "$1"
}
}
}
},
"requestErrorTitle": { "message": "Page Shot is out of order." },
"requestErrorDetails": { "message": "Your shot was not saved. We apologize for the inconvenience. Try again soon." },
"connectionErrorTitle": { "message": "Cannot connect to the Page Shot server." },
"connectionErrorDetails": { "message": "There may be a problem with the service or with your network connection." },
"loginErrorDetails": { "message": "Your shot was not saved. There was an error authenticating with the server." },
"loginConnectionErrorDetails": { "message": "There may be a problem with the service or your network connection." },
"unshootablePageErrorTitle": { "message": "Page cannot be screenshotted." },
"unshootablePageErrorDetails": { "message": "This is not a normal web page, and Page Shot cannot capture screenshots from it." },
"selfScreenshotErrorTitle": { "message": "You can’t take a shot of a Page Shot page!" },
"genericErrorTitle": { "message": "Page Shot went haywire." },
"genericErrorDetails": { "message": "Try again or take a shot on another page?" }
}
"requestErrorTitle": {
"message": "Page Shot is out of order."
},
"requestErrorDetails": {
"message": "Your shot was not saved. We apologize for the inconvenience. Try again soon."
},
"connectionErrorTitle": {
"message": "Cannot connect to the Page Shot server."
},
"connectionErrorDetails": {
"message": "There may be a problem with the service or with your network connection."
},
"loginErrorDetails": {
"message": "Your shot was not saved. There was an error authenticating with the server."
},
"loginConnectionErrorDetails": {
"message": "There may be a problem with the service or your network connection."
},
"unshootablePageErrorTitle": {
"message": "Page cannot be screenshotted."
},
"unshootablePageErrorDetails": {
"message": "This is not a normal web page, and Page Shot cannot capture screenshots from it."
},
"selfScreenshotErrorTitle": {
"message": "You can’t take a shot of a Page Shot page!"
},
"genericErrorTitle": {
"message": "Page Shot went haywire."
},
"genericErrorDetails": {
"message": "Try again or take a shot on another page?"
}
}
165 changes: 165 additions & 0 deletions bin/pontoon-to-webext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
#! /usr/bin/env node

/* eslint-disable promise/avoid-new */

const propertiesParser = require('properties-parser');
const path = require('path');
const FS = require('q-io/fs');
const argv = require('minimist')(process.argv.slice(2));

const Habitat = require('habitat');
Habitat.load();

const regexPlaceholders = /\{([A-Za-z0-9_@]*)\}/g;
let supportedLocales = process.env.SUPPORTED_LOCALES || '*';

const config = {
'dest': argv.dest || 'dist/_locales',
'src': argv.src || 'locales',
'default_locale': argv.locale || 'en-US'
};

function log(...args) {
console.log(...args); // eslint-disable-line no-console
}

function error(...args) {
console.error(...args); // eslint-disable-line no-console
}

function getListLocales() {
return new Promise((resolve, reject) => {
if (supportedLocales === '*') {
FS.listDirectoryTree(path.join(process.cwd(), config.src)).then((dirTree) => {
const localeList = [];

// Get rid of the top level, we're only interested with what's inside it
dirTree.splice(0,1);
dirTree.forEach((localeLocation) => {
// Get the locale code from the end of the path. We're expecting the structure of Pontoon's output here
const langcode = localeLocation.split(path.sep).slice(-1)[0];

if (langcode) {
localeList.push(langcode);
}
});
return resolve(localeList);
}).catch((e) => {
reject(e);
});
} else {
supportedLocales = supportedLocales.split(',').map(item => item.trim());
resolve(supportedLocales);
}
});
}

function writeFiles(entries) {
for (const entry of entries) {
const publicPath = path.join(process.cwd(), config.dest, entry.locale.replace('-', '_'));
const localesPath = path.join(publicPath, 'messages.json');

FS.makeTree(publicPath).then(() => {
return FS.write(localesPath, JSON.stringify(entry.content, null, 2));
}).then(() => {
log(`Done compiling locales at: ${localesPath}`);
}).catch((e) => {
error(e);
});
}
}

function readPropertiesFile(filePath) {
return new Promise((resolve, reject) => {
propertiesParser.read(filePath, (messageError, messageProperties) => {
if (messageError && messageError.code !== 'ENOENT') {
return reject(messageError);
}
resolve(messageProperties);
});
});
}

function getContentPlaceholders() {
return new Promise((resolve, reject) => {
FS.listTree(path.join(process.cwd(), config.src, config.default_locale), (filePath) => {
return path.extname(filePath) === '.properties';
}).then((files) => {
return Promise.all(files.map(readPropertiesFile)).then((properties) => {
const mergedPlaceholders = {};

properties.forEach(messages => {
const placeholders = {};
Object.keys(messages).forEach(key => {
const message = messages[key];
if (message.indexOf('{') !== -1) {
const placeholder = {};
let index = 1;
message.replace(regexPlaceholders, (item, key) => {
placeholder[key.toLowerCase()] = { content: `$${index}` };
index++;
});
placeholders[key] = placeholder;
}
});
Object.assign(mergedPlaceholders, placeholders);
});

resolve(mergedPlaceholders);
});
}).catch((e) => {
reject(e);
});
});
}

function getContentMessages(locale, placeholders) {
return new Promise((resolve, reject) => {
FS.listTree(path.join(process.cwd(), config.src, locale), (filePath) => {
return path.extname(filePath) === '.properties';
}).then((files) => {
return Promise.all(files.map(readPropertiesFile)).then((properties) => {
const mergedProperties = {};

properties.forEach(messages => {
Object.keys(messages).forEach(key => {
let message = messages[key];
messages[key] = { 'message': message };
if (placeholders[key]) {
message = message.replace(regexPlaceholders, (item, key) => `\$${key.toUpperCase()}\$`);
messages[key] = {
'message': message,
'placeholders': placeholders[key]
};
}
});
Object.assign(mergedProperties, messages);
});

resolve({content: mergedProperties, locale: locale});
});
}).catch((e) => {
reject(e);
});
});
}

function processMessageFiles(locales) {
if (!locales) {
error('List of locales was undefined. Cannot run pontoon-to-webext.');
process.exit(1);
}
if (locales.length === 0) {
error('Locale list is empty. Cannot run pontoon-to-webext.');
process.exit(1);
}
log(`processing the following locales: ${locales.toString()}`);
return getContentPlaceholders().then(placeholders => {
return Promise.all(locales.map(locale => getContentMessages(locale, placeholders)));
});
}

getListLocales().then(processMessageFiles)
.then(writeFiles).catch((err)=> {
error(err);
});
27 changes: 27 additions & 0 deletions locales/en-US/webextension.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
addonDescription = Page Shot takes clips and screenshots from pages, and can save a permanent copy of a page.
addonAuthorsList = Ian Bicking, Donovan Preston, and Bram Pitoyo
toolbarButtonLabel = Take a shot
contextMenuLabel = Create Page Shot
myShotsLink = My Shots
screenshotInstructions = Drag or click on the page to select a region. Press ESC to cancel.
saveScreenshotSelectedArea = Save
saveScreenshotVisibleArea = Save visible
saveScreenshotFullPage = Save full page
cancelScreenshot = Cancel
downloadScreenshot = Download
notificationLinkCopiedTitle = Link Copied
# LOCALIZATION NOTE(notificationLinkCopiedDetails): The string "{meta_key}-V" should be translated
# to the region-specific shorthand for the Paste keyboard shortcut. {meta_key} is a placeholder for
# the modifier key used to complete the paste: for example, Ctrl-V on Windows systems.
notificationLinkCopiedDetails = The link to your shot has been copied to the clipboard. Press {meta_key}-V to paste.
requestErrorTitle = Page Shot is out of order.
requestErrorDetails = Your shot was not saved. We apologize for the inconvenience. Try again soon.
connectionErrorTitle = Cannot connect to the Page Shot server.
connectionErrorDetails = There may be a problem with the service or with your network connection.
loginErrorDetails = Your shot was not saved. There was an error authenticating with the server.
loginConnectionErrorDetails = There may be a problem with the service or your network connection.
unshootablePageErrorTitle = Page cannot be screenshotted.
unshootablePageErrorDetails = This is not a normal web page, and Page Shot cannot capture screenshots from it.
selfScreenshotErrorTitle = You can’t take a shot of a Page Shot page!
genericErrorTitle = Page Shot went haywire.
genericErrorDetails = Try again or take a shot on another page?
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
"envc": "2.4.1",
"escape-html": "1.0.3",
"express": "4.14.1",
"habitat": "^3.1.2",
"jpm": "1.3.0",
"keygrip": "1.0.1",
"minimist": "^1.2.0",
"mobile-detect": "1.3.5",
"morgan": "1.7.0",
"mozlog": "2.0.6",
Expand All @@ -26,6 +28,8 @@
"nodify-uuid": "0.0.1",
"pg": "6.1.2",
"pg-patcher": "0.3.0",
"properties-parser": "^0.3.1",
"q-io": "^1.13.2",
"raven": "1.1.1",
"raven-js": "3.10.0",
"react": "15.4.2",
Expand All @@ -52,6 +56,7 @@
"node-sass": "4.4.0",
"npm-run-all": "4.0.1",
"nsp": "2.6.2",
"properties-parser": "^0.3.1",
"sass-lint": "1.10.2",
"selenium-webdriver": "3.0.1",
"svgo": "0.7.2",
Expand Down

0 comments on commit 7d3e6ef

Please sign in to comment.