The site must support translation/localization. This includes supporting right-to-left languages.
The site must be fully functional without JavaScript. (We expect a significant proportion of our users to be running NoScript.)
The site must support desktop and mobile screen sizes, without using JS or subdomains.
The site must be somewhat usable by IE7+. A lot of our user base uses very old browsers. For example, CloudFlare found that only 52% of requests from Iran come from “modern” browsers.
The site must be able to be served from static storage (i.e., S3 bucket).
The site resources must be served from the same domain as the site itself (i.e., no external JS resources, fonts, etc.). This closes some potential security holes.
The site must be able to be served from any sub-path of a domain. That is, the same site must be simultaneously accessible from xyz.example.com/blah/index.html and from example.com/abc/blah/index.html. This is because we serve from https://s3.amazonaws.com/f58p-mqce-k1yj/en/index.html and from http://play.psiphon3.com/en/index.html and even more exotic anti-blocking locations.
The site should be fairly fast to load -- images that aren’t too large, etc. A lot of our users are on degraded connections.
-
Install Node.js.
-
Install all dependencies:
$ npm ci
$ cd plugins/languagemaker && npm ci . && cd -
$ cd plugins/languageredirector && npm ci . && cd -
# Or maybe: find . -name package.json -maxdepth 3 -execdir npm ci \;
- Generate site, serve it, and monitor for changes:
$ node --max-old-space-size=8192 --max-semi-space-size=512 --nouse-idle-notification node_modules/docpad/out/bin/docpad.js run --cache --offline
# Site builds can be quite slow. Skip language generation for faster builds:
# npx docpad run --cache --offline --env fastbuild
The use of a complicated node
command instead of docpad
is so that the generate process has more memory to work with. Note that it's probably more memory-reasonable to generate the site separately from serving it (using python -m SimpleHTTPServer 9778
or whatever). See below for simply generating the site.
The commands necessary to do a constant-memory generation have been collected in a script.
$ ./generate.py
This website gets run both at the root of a domain (e.g. http://psiphon3.com/
) and under a subdirectory of a domain -- specifically, under a S3 bucket (e.g., https://s3.amazonaws.com/57wj-4j1q-wa7e/
). This makes it difficult to refer to other pages and resources with href
and src
attributes from within the site -- /styles/style.css
won't work when the site is under a subdirectory. One approach would be to hand-craft relative paths for each and page, but this is very brittle and a total pain when using layouts/templates for different pages at different path depths.
So we use a Docpad plugin that provides the relative path to root for each document, and then a global function that we can use wherever we want to provide a path to a page or resource. So, the src
attribute for an image would look like this:
src="<%= @relativeToRoot '/images/path/filename.png' %>"
If the page containing that line is located at /en/blog/index.html.eco
, then it will render to:
src="../../images/path/filename.png"
And the path will remain correct regardless of the path location of the containing page, and whether the site is hosted at root or in a bucket/subdirectory.
Serving the site with Docpad will only test the site-at-root scenario. Testing the site-under-subdirectory scenario can be done like so:
- Run a static site server in the root directory of the website source (i.e., the directory of this README lives in). (You can use Python's
SimpleHTTPServer
or Node'snode-static
package, or whatever.) - In your browser, go to
http://localhost:8080/out/
. - You'll be redirected to
http://localhost:8080/out/en/index.html
. The site is effectively under theout
directory. - Browse around. Watch the console for errors.
An easy way to generate a QR code is via the Google Chart API. Just put the target URL at the end of this URL: https://chart.googleapis.com/chart?chs=150x150&cht=qr&chld=M|0&chl=
. For example, here's a QR for psiphon3.com. (NOTE: The Google Chart API QR for the Play Store dev page wasn't being read correctly by the Android Camera reader. Be wary and consider making a new QR generator the go-to tool.)
Enabling sponsor HTML snippets for a bucket site mostly involves configuring sponsor_snippet_pull.py
to retrieve the desired snippet and upload it to the correct bucket. The page code will detect the presence of snippet, sanitize, and load it.
JavaScript will be stripped completely from the snippet. <style>
blocks and external CSS files will be stripped, but inline styles and CSS class names will be left intact.
Blog posts are originally written in English, and will typically be written using Markdown; here is an example of one.
Translations can be written using either Markdown or HTML. We're not yet sure if Markdown is entirely happy with i18n, but it's probably best to try that first. Markdown is simpler to write in, HTML provides more control.
If the translation is done in HTML, it should only contain the inner body of the post, and not <html>
, <head>
, or <body>
tags; here is an example of one.
At the top of the raw blog post example above you will see the post "metadata". The title
must be translated and the author
may be transliterated if appropriate for the target language. The layout
must not be changed.
It's quite easy to add images (screenshots, etc.) that are locale-specific.
-
Create an English version of the image. This will also be used as a fallback if a locale-specific image doesn't exist. Give it a filename of the form
whatever.en.ext
. For example,i18n-test.en.png
. -
In the page where you want to use it, create an
img
tag that looks something like:<img src="<%- @relativeToRoot @ttURL '/images/i18n-test.png' %>">
(Note that you should also provide an
alt
attribute and you probably also wantclass="img-responsive"
.)ttURL
is defined indocpad.coffee
. If the document currently being generated is in Farsi, it checks to see if there exists a file with the name/images/i18n-test.fa.png
, uses it if it exists, and falls back to/images/i18n-test.en.png
or/images/i18n-test.png
if not. -
Create the localized images. Save them with appropriate filenames. E.g.:
i18n-test.fa.png
,i18n-test.zh.png
,i18n-test.ug@Latn.png
, etc. There doesn't need to (immediately) be one for each supported language, because the English fallback will compensate for missing images.
Note that ttURL
could also be used for files other than images. Video? CSS?
Google Analytics is not enabled by default, but can be enabled by adding a file to src/files/assets/
named google-analytics-id
containing the Google Analytics tracking ID (like UA-XXXXX-XX
) that should be used.
Metadata in layouts is basically ignored (besides another layout
in the chain). There's an issue to improve this. This is very unfortunate for us because we put almost all content into layouts. And, for example, it would be best to put a page's keywords into the layout rather than in documents/en/blah.html
.
See the LICENSE
file.