feat(localization): add localization for static strings

This commit is contained in:
David Litvak
2017-11-01 15:37:15 +01:00
committed by Benedikt Rötsch
parent eaec09a594
commit 68a8052bdf
24 changed files with 333 additions and 99 deletions

12
app.js
View File

@@ -11,6 +11,7 @@ const helmet = require('helmet')
require('dotenv').config({ path: 'variables.env' })
const helpers = require('./helpers')
const { translate, initializeTranslations } = require('./i18n/i18n')
const breadcrumb = require('./lib/breadcrumb')
const routes = require('./routes/index')
const { initClients, getSpace } = require('./services/contentful')
@@ -30,7 +31,6 @@ app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: false }))
app.use(cookieParser())
app.use(express.static(path.join(__dirname, 'public')))
app.use(breadcrumb())
// Force all requests on production to be served over https
app.use(function (req, res, next) {
@@ -84,11 +84,11 @@ app.use(async function (request, response, next) {
const apis = [
{
id: 'cda',
label: 'Delivery (published content)'
label: 'Delivery'
},
{
id: 'cpa',
label: 'Preview (draft content)'
label: 'Preview'
}
]
@@ -111,6 +111,10 @@ app.use(async function (request, response, next) {
response.locals.currentLocale = defaultLocale
}
// Initialize translations and include them on templates
initializeTranslations()
response.locals.translate = translate
// Inject custom helpers
response.locals.helpers = helpers
@@ -123,6 +127,8 @@ app.use(async function (request, response, next) {
next()
})
app.use(breadcrumb())
// Initialize the route handling
// Check ./routes/index.js to get a list of all implemented routes
app.use('/', routes)

View File

@@ -1,4 +1,5 @@
const marked = require('marked')
const { translate } = require('./i18n/i18n')
// Parse markdown text
module.exports.markdown = (content = '') => {
@@ -11,11 +12,11 @@ module.exports.markdown = (content = '') => {
// A handy debugging function we can use to sort of "console.log" our data
module.exports.dump = (obj) => JSON.stringify(obj, null, 2)
module.exports.formatMetaTitle = (title) => {
module.exports.formatMetaTitle = (title, localeCode = 'en-US') => {
if (!title) {
return 'The Example App'
return translate('defaultTitle', localeCode)
}
return `${title.charAt(0).toUpperCase()}${title.slice(1)}The Example App`
return `${title.charAt(0).toUpperCase()}${title.slice(1)}${translate('defaultTitle', localeCode)}`
}
/**

46
i18n/i18n.js Normal file
View File

@@ -0,0 +1,46 @@
const fs = require('fs')
const path = require('path')
let translations = null
// Initializes translation dictionary with contents from /public/locales
module.exports.initializeTranslations = () => {
if (translations) {
return
}
translations = {}
const localesPath = path.join(__dirname, '..', 'public', 'locales')
try {
const files = fs.readdirSync(localesPath)
files.forEach((file) => {
const localeDict = require(path.join(localesPath, file))
translations[file.replace('.json', '')] = localeDict
})
} catch (error) {
console.error('Error loading localization files:')
console.error(error)
}
}
/**
* Translate a static string
* @param symbol string Identifier for static text
* @param locale string Locale code
*
* @returns string
*/
module.exports.translate = (symbol, locale = 'en-US') => {
const localeDict = translations[locale]
if (!localeDict) {
return `Localization file for ${locale} is not available`
}
const translatedValue = localeDict[symbol]
if (!translatedValue) {
return `Translation not found for ${symbol} in ${locale}`
}
return translatedValue
}

View File

@@ -1,4 +1,5 @@
const url = require('url')
const { translate } = require('../i18n/i18n')
module.exports = (modifier) => {
return (request, response, next) => {
@@ -6,7 +7,10 @@ module.exports = (modifier) => {
const urlComponents = url.parse(request.url).pathname.split('/').filter(Boolean)
let breadcrumbs = []
breadcrumbs.push({ label: 'Home', url: baseUrl })
breadcrumbs.push({
label: translate('homeLabel', response.locals.currentLocale.code),
url: baseUrl
})
// Map components of the path to breadcrumbs with resolvable URLs
let mappedComponents = urlComponents.map((component, i, array) => {
const path = array.slice(0, i + 1).join('/')

77
public/locales/de-DE.json Normal file
View File

@@ -0,0 +1,77 @@
{
"defaultTitle": "Die Beispielanwendung",
"whatIsThisApp": "Was ist die Beispielanwendung?",
"metaDescription": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen.",
"metaTwitterCard": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen.",
"metaImageAlt": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen.",
"metaImageDescription": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen.",
"viewOnGithub": "Auf GitHub ansehen",
"apiSwitcherHelp": "Ansehen des veröffentlichten und unveröffentlichten Inhalts durch Wechsel von Delivery und Preview APIs.",
"apiLabelHelpcda": "(veröffentlichter Inhalt)",
"apiLabelHelpcpa": "(unveröffentlichter Inhalt)",
"locale": "Sprache",
"localeQuestion": "Sie arbeiten mit verschiedenen Sprachen? Dann können Sie die Sprache für Anfragen an die Content Delivery API definieren.",
"settingsLabel": "Einstellungen",
"logoAlt": "Die Beispielanwendung für Contentful",
"homeLabel": "Home",
"coursesLabel": "Kurse",
"footerDisclaimer": "Powered by Contentful. Diese Website und deren Materialien existieren nur für Demonstrationszwecken. Sie können diese benutzen, um den Inhalt ihres Contentful Kontos anzusehen.",
"imprintLabel": "Impressum",
"contactUsLabel": "Uns Kontaktieren",
"modalTitle": "Ein referenzierbares Beispiel für Entwickler, die Contentful benutzen.",
"modalIntro": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen. Sie stellt eine Plattform fürs online Lernen dar, die mithilfe von Contentful gebaut wurde. (sehr meta!)",
"modalCodeIntro": "Wenn Sie es bevorzugen, sich die Hände schmutzig zu machen, sehen sie sich die Anwendung hier an",
"modalCTALabel": "Gut, verstanden.",
"editorialFeaturesHint": "Bearbeiten Sie diesen Eintrag in unserer Web App. Sie müssen sich eingelogged haben und Zugang zum Space haben, um diese Funktion nutzen zu können.",
"draftLabel": "Entwurf",
"pendingChangesLabel": "Änderungen vorbehalten",
"lessonModuleErrorTitle": "⚠️ Ungültiges Lektionsmodul",
"lessonModuleErrorBody": "Konnte den Typ nicht erkennen: ",
"nextLessonLabel": "Nächste Lektion ansehen",
"imageErrorTitle": "⚠️ Bild fehlt",
"viewCourseLabel": "Kurs ansehen",
"categoriesWelcomeLabel": "Willkommen zur folgenden Kategorie: ",
"sitemapWelcomeLabel": "Willkommen zur folgenden Kategorie: ",
"tableOfContentsLabel": "Inhalt",
"courseOverviewLabel": "Kurs Übersicht",
"overviewLabel": "Übersicht",
"durationLabel": "Dauer",
"minutesLabel": "min",
"skillLevelLabel": "Komplexität",
"startCourseLabel": "Kurs beginnen",
"categoriesLabel": "Kategorien",
"allCoursesLabel": "Alle Kurse",
"somethingWentWrongLabel": "Hmm, etwas ging schief.",
"tryLabel": "Versuchen Sie",
"contentModelChangedErrorLabel": "Überprüfen Sie, ob das Content Model verändert wurde.",
"draftOrPublishedErrorLabel": "Überprüfen Sie, ob es nicht veröffentlichte Änderungen gibt.",
"localeContentErrorLabel": "Überprüfen Sie, ob es Inhalt für diese Sprache gibt.",
"verifyCredentialsErrorLabel": "Überprüfen Sie, ob die Zugangsdaten richtig und nicht abgelaufen sind.",
"stackTraceErrorLabel": "Schauen Sie sich den folgenden Stack Trace an",
"errorLabel": "Fehler",
"stackTraceLabel": "Stack Trace",
"companyLabel": "Firma",
"officeLabel": "Büro in Berlin",
"germanyLabel": "Deutschland",
"registrationCourtLabel": "Amtsgericht",
"managingDirectorLabel": "Verwalter",
"vatNumberLabel": "Steuernummer",
"settingsIntroLabel": "Um Inhalt von unseren APIs zu bekommen, müssen Anwendungen von Kunden sich authentifizieren, sowohl mit der Space ID als auch dem Access Token.",
"changesSavedLabel": "Änderungen erfolgreich gespeichert!",
"errorOcurredTitleLabel": "Fehler aufgetreten",
"errorOcurredMessageLabel": "Einige Fehler sind aufgetreten. Bitte schauen Sie sich die Fehlermeldungen neben den Feldern an.",
"connectedToSpaceLabel": "Mit einem Space verbinden.",
"spaceIdLabel": "Space ID",
"spaceIdHelpText": "Die Space Id ist eine eindeutige Identifizierung für Ihren Space.",
"accessTokenLabel": "Access Token",
"contentDeliveryApiHelpText": "Schauen Sie sich veröffentlichten Inhalt mit dieser API an.",
"contentPreviewApiHelpText": "Schauen Sie sich unveröffentlichten Inhalt mit dieser API an. (z.B. Inhalt im Zustand “Entwurf”).",
"enableEditorialFeaturesLabel": "Editoriale Funktionen aktivieren.",
"enableEditorialFeaturesHelpText": "Aktivieren, um Bearbeitung und weitere kontextabhängige Helferlein zu aktivieren. Damit dies funktioniert, müssen sie Zugang zu dem Space haben.",
"saveSettingsButtonLabel": "Einstellungen Speichern",
"fieldIsRequiredLabel": "Diese Feld ist notwendig.",
"deliveryKeyInvalidLabel": "Ihr Delivery API Zugangsschlüssel ist ungültig.",
"spaceOrTokenInvalid": "Dieser Space existiert nicht, oder Ihr Access Token kommt nicht von diesem Space.",
"somethingWentWrongLabel": "Irgendetwas lief falsch.",
"previewKeyInvalidLabel": "Ihr Preview API Zugangsschlüssel ist ungültig."
}

77
public/locales/en-US.json Normal file
View File

@@ -0,0 +1,77 @@
{
"defaultTitle": "The Example App",
"whatIsThisApp": "What is this example app?",
"metaDescription": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.",
"metaTwitterCard": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.",
"metaImageAlt": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.",
"metaImageDescription": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.",
"viewOnGithub": "View on Github",
"apiSwitcherHelp": "View the published or draft content by simply switching between the Deliver and Preview APIs.",
"apiLabelHelpcda": "(published content)",
"apiLabelHelpcpa": "(draft content)",
"locale": "Locale",
"localeQuestion": "Working with multiple languages? You can query the Content Delivery API for a specific locale.",
"settingsLabel": "Settings",
"logoAlt": "Contentful Example App",
"homeLabel": "Home",
"coursesLabel": "Courses",
"footerDisclaimer": "Powered by Contentful. This website and the materials found on it are for demo purposes. You can use this to preview the content created on your Contentful account.",
"imprintLabel": "Imprint",
"contactUsLabel": "Contact us",
"modalTitle": "A referenceable example for developers using Contentful",
"modalIntro": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful. This app is an online learning platform which teaches you how Contentful was used to build this app (so meta)!",
"modalCodeIntro": "If you prefer to start by getting your hands dirty with code, check out this app on",
"modalCTALabel": "Ok, got it.",
"editorialFeaturesHint": "Edit this entry in our web app. You have to be logged in and have access to the connected space to use this feature.",
"draftLabel": "draft",
"pendingChangesLabel": "pending changes",
"lessonModuleErrorTitle": "⚠️ Invalid lesson module",
"lessonModuleErrorBody": "Could not determine type of",
"nextLessonLabel": "View next lesson",
"imageErrorTitle": "⚠️ Image missing",
"viewCourseLabel": "view course",
"categoriesWelcomeLabel": "Welcome to",
"sitemapWelcomeLabel": "Welcome to",
"tableOfContentsLabel": "Table of contents",
"courseOverviewLabel": "Cource overview",
"overviewLabel": "Overview",
"durationLabel": "Duration",
"minutesLabel": "min",
"skillLevelLabel": "Skill level",
"startCourseLabel": "Start course",
"categoriesLabel": "Categories",
"allCoursesLabel": "All courses",
"somethingWentWrongLabel": "Oops Something went wrong",
"tryLabel": "Try",
"contentModelChangedErrorLabel": "Check if the content model has changed",
"draftOrPublishedErrorLabel": "Check the selection has content in draft or published state (for Preview or Delivery)",
"localeContentErrorLabel": "Check if there's any content for this locale",
"verifyCredentialsErrorLabel": "Verify credentials are correct and up to date",
"stackTraceErrorLabel": "Check the stack trace below",
"errorLabel": "Error",
"stackTraceLabel": "Stack trace",
"companyLabel": "Company",
"officeLabel": "Office Berlin",
"germanyLabel": "Germany",
"registrationCourtLabel": "Registration Court",
"managingDirectorLabel": "Managing Director",
"vatNumberLabel": "VAT Number",
"settingsIntroLabel": "To query and get content using the APIs, client applications need to authenticate with both the Space ID and an access token.",
"changesSavedLabel": "Changes saved successfully!",
"errorOcurredTitleLabel": "Error occurred",
"errorOcurredMessageLabel": "Some errors occurred. Please check the error messages next to the fields.",
"connectedToSpaceLabel": "Connected to space",
"spaceIdLabel": "Space ID",
"spaceIdHelpText": "The Space ID is a unique identifier for your space.",
"accessTokenLabel": "access token",
"contentDeliveryApiHelpText": "View published content using this API.",
"contentPreviewApiHelpText": "Preview unpublished content using this API (i.e. content with “Draft” status).",
"enableEditorialFeaturesLabel": "Enable editorial features",
"enableEditorialFeaturesHelpText": "Enable to display an edit link and other contextual helpers for authors. You need to have access to the connected space to make this work.",
"saveSettingsButtonLabel": "Save settings",
"fieldIsRequiredLabel": "This field is required",
"deliveryKeyInvalidLabel": "Your Delivery API key is invalid.",
"spaceOrTokenInvalid": "This space does not exist or your access token is not associated with your space.",
"somethingWentWrongLabel": "Something went wrong",
"previewKeyInvalidLabel": "Your Preview API key is invalid."
}

View File

@@ -11,6 +11,7 @@ const {
const attachEntryState = require('../lib/entry-state')
const { updateCookie } = require('../lib/cookies')
const { translate } = require('../i18n/i18n')
/**
* Renders courses list when `/courses` route is requested
@@ -33,7 +34,11 @@ module.exports.getCourses = async (request, response, next) => {
}
categories = await getCategories(response.locals.currentLocale.code, response.locals.currentApi.id)
response.render('courses', { title: `All Courses (${courses.length})`, categories, courses })
response.render('courses', {
title: `${translate('allCoursesLabel', response.locals.currentLocale.code)} (${courses.length})`,
categories,
courses
})
}
/**

View File

@@ -1,3 +1,4 @@
const { translate } = require('../i18n/i18n')
/**
* Renders imprint page when `/imprint` route is requested
* @param request - Object - Express request
@@ -6,6 +7,7 @@
* @returns {undefined}
*/
module.exports.getImprint = (request, response, next) => {
response.render('imprint', { title: 'Imprint' })
response.render('imprint', {
title: translate('imprintLabel', response.locals.currentLocale.code)
})
}

View File

@@ -33,4 +33,3 @@ module.exports.getLandingPage = async (request, response, next) => {
response.render('landingPage', { title: pathname, landingPage })
}

View File

@@ -5,6 +5,7 @@
const { createClient } = require('contentful')
const { initClients, getSpace } = require('./../services/contentful')
const { updateCookie } = require('../lib/cookies')
const { translate } = require('../i18n/i18n')
const SETTINGS_NAME = 'theExampleAppSettings'
@@ -20,7 +21,7 @@ async function renderSettings (response, opts) {
}
response.render('settings', {
title: 'Settings',
title: translate('settingsLabel', response.locals.currentLocale.code),
errors: {},
hasErrors: false,
success: false,
@@ -56,6 +57,7 @@ module.exports.getSettings = async (request, response, next) => {
* @returns {undefined}
*/
module.exports.postSettings = async (request, response, next) => {
const currentLocale = response.locals.currentLocale
const errorList = []
const { spaceId, deliveryToken, previewToken, editorialFeatures } = request.body
const settings = {
@@ -69,21 +71,21 @@ module.exports.postSettings = async (request, response, next) => {
if (!spaceId) {
errorList.push({
field: 'spaceId',
message: 'This field is required'
message: translate('fieldIsRequiredLabel', currentLocale.code)
})
}
if (!deliveryToken) {
errorList.push({
field: 'deliveryToken',
message: 'This field is required'
message: translate('fieldIsRequiredLabel', currentLocale.code)
})
}
if (!previewToken) {
errorList.push({
field: 'previewToken',
message: 'This field is required'
message: translate('fieldIsRequiredLabel', currentLocale.code)
})
}
@@ -98,17 +100,17 @@ module.exports.postSettings = async (request, response, next) => {
if (err.response.status === 401) {
errorList.push({
field: 'deliveryToken',
message: 'Your Delivery API key is invalid.'
message: translate('deliveryKeyInvalidLabel', currentLocale.code)
})
} else if (err.response.status === 404) {
errorList.push({
field: 'spaceId',
message: 'This space does not exist or your access token is not associated with your space.'
message: translate('spaceOrTokenInvalid', currentLocale.code)
})
} else {
errorList.push({
field: 'deliveryToken',
message: `Something went wrong: ${err.response.data.message}`
message: `${translate('somethingWentWrongLabel', currentLocale.code)}: ${err.response.data.message}`
})
}
}
@@ -126,17 +128,17 @@ module.exports.postSettings = async (request, response, next) => {
if (err.response.status === 401) {
errorList.push({
field: 'previewToken',
message: 'Your Preview API key is invalid.'
message: translate('previewKeyInvalidLabel', currentLocale.code)
})
} else if (err.response.status === 404) {
errorList.push({
field: 'spaceId',
message: 'This space does not exist or your delivery token is not associated with your space.'
message: translate('spaceOrTokenInvalid', currentLocale.code)
})
} else {
errorList.push({
field: 'previewToken',
message: `Something went wrong: ${err.response.data.message}`
message: `${translate('somethingWentWrongLabel', currentLocale.code)}: ${err.response.data.message}`
})
}
}
@@ -171,4 +173,3 @@ module.exports.postSettings = async (request, response, next) => {
success: errorList.length === 0
})
}

View File

@@ -7,7 +7,7 @@ describe('courses', () => {
return request(app).get('/courses')
.expect(200)
.then((response) => {
expect(response.text.match(/<h1>All Courses /)).toBeTruthy()
expect(response.text.match(/<h1>All courses /)).toBeTruthy()
})
})
test('it should render a course', () => {

View File

@@ -2,6 +2,7 @@
const { getCourses, getCourse, getCoursesByCategory, getLesson } = require('../../routes/courses')
const { getSettings } = require('../../routes/settings')
const { mockCourse, mockCategory } = require('./mocks/index')
const { translate, initializeTranslations } = require('../../i18n/i18n')
jest.mock('../../services/contentful')
const contentful = require('../../services/contentful')
@@ -25,6 +26,8 @@ const response = {
}
beforeAll(() => {
initializeTranslations()
contentful.getCourses.mockImplementation(() => [mockCourse])
contentful.getCourse.mockImplementation(() => mockCourse)
@@ -45,7 +48,7 @@ describe('Courses', () => {
test('it should courses list once', async () => {
await getCourses(request, response)
expect(response.render.mock.calls[0][0]).toBe('courses')
expect(response.render.mock.calls[0][1].title).toBe('All Courses (1)')
expect(response.render.mock.calls[0][1].title).toBe('All courses (1)')
expect(response.render.mock.calls[0][1].courses.length).toBe(1)
expect(response.render.mock.calls.length).toBe(1)
})
@@ -88,3 +91,17 @@ describe('Settings', () => {
expect(response.render.mock.calls[0][1].settings).toBe(response.locals.settings)
})
})
describe('i18n', () => {
test('It returns an error when locale file is not found', () => {
expect(translate('foo', 'bar-locale')).toBe('Localization file for bar-locale is not available')
})
test('It returns an error when symbol is not found on locale file', () => {
expect(translate('foo', 'en-US')).toBe('Translation not found for foo in en-US')
})
test('It returns the translated string when symbol is found on locale file', () => {
expect(translate('coursesLabel', 'en-US')).toBe('Courses')
})
})

View File

@@ -10,12 +10,12 @@ block content
.layout-sidebar
section.layout-sidebar__sidebar
.layout-sidebar__sidebar-header
h2.layout-sidebar__sidebar-title Table of contents
h2.layout-sidebar__sidebar-title #{translate('tableOfContentsLabel', currentLocale.code)}
.layout-sidebar__sidebar-content
.table-of-contents
.table-of-contents__list
.table-of-contents__item
a.table-of-contents__link(href=`/courses/${course.fields.slug}${queryString}` class=(currentPath.endsWith(course.fields.slug) ? 'active' : '') class=(visitedLessons.includes(course.sys.id) ? 'visited' : '')) Course overview
a.table-of-contents__link(href=`/courses/${course.fields.slug}${queryString}` class=(currentPath.endsWith(course.fields.slug) ? 'active' : '') class=(visitedLessons.includes(course.sys.id) ? 'visited' : '')) #{translate('courseOverviewLabel', currentLocale.code)}
each l in course.fields.lessons
if l.fields
.table-of-contents__item
@@ -28,17 +28,17 @@ block content
h1.course__title= course.fields.title
+editorialFeatures(course)
.course__overview
h3.course__overview-title Overview
h3.course__overview-title #{translate('overviewLabel', currentLocale.code)}
if course.fields.duration
.course__overview-item
svg.course__overview-icon
use(xlink:href='/icons/icons.svg#duration')
.course__overview-value Duration: #{course.fields.duration} min
.course__overview-value #{translate('durationLabel', currentLocale.code)}: #{course.fields.duration} #{translate('minutesLabel', currentLocale.code)}
if course.fields.skillLevel
.course__overview-item
svg.course__overview-icon
use(xlink:href='/icons/icons.svg#skill-level')
.course__overview-value Skill level: #{course.fields.skillLevel}
.course__overview-value #{translate('skillLevelLabel', currentLocale.code)}: #{course.fields.skillLevel}
.course__overview-cta-wrapper
a.course__overview-cta.cta(href=`/courses/${course.fields.slug}/lessons/${course.fields.lessons[0].fields.slug}${queryString}`) Start course
a.course__overview-cta.cta(href=`/courses/${course.fields.slug}/lessons/${course.fields.lessons[0].fields.slug}${queryString}`) #{translate('startCourseLabel', currentLocale.code)}
.course__description !{helpers.markdown(course.fields.description)}

View File

@@ -9,12 +9,12 @@ block content
.layout-sidebar
section.layout-sidebar__sidebar
.layout-sidebar__sidebar-header
h2.layout-sidebar__sidebar-title Categories
h2.layout-sidebar__sidebar-title #{translate('categoriesLabel', currentLocale.code)}
.layout-sidebar__sidebar-content
.sidebar-menu
ul.sidebar-menu__list
li.sidebar-menu__item
a.sidebar-menu__link(href=`/courses${queryString}` class=(currentPath.endsWith('/courses') ? 'active' : '')) All courses
a.sidebar-menu__link(href=`/courses${queryString}` class=(currentPath.endsWith('/courses') ? 'active' : '')) #{translate('allCoursesLabel', currentLocale.code)}
each category in categories
li.sidebar-menu__item
a.sidebar-menu__link(href=`/courses/categories/${category.fields.slug}${queryString}` class=(currentPath.endsWith(category.fields.slug) ? 'active' : '')) #{category.fields.title}

View File

@@ -6,18 +6,18 @@ block content
.layout-centered
+breadcrumb
.error
h1 Oops Something went wrong (#{error.status})
h2 Try:
h1 #{translate('somethingWentWrongLabel', currentLocale.code)} (#{error.status})
h2 #{translate('tryLabel', currentLocale.code)}:
ul
li Check if the content model has changed
li Check the selection has content in draft or published state (for Preview or Delivery)
li Check if there's any content for this locale
li Verify credentials are correct and up to date
li Check the stack trace below
li #{translate('contentModelChangedErrorLabel', currentLocale.code)}
li #{translate('draftOrPublishedErrorLabel', currentLocale.code)}
li #{translate('localeContentErrorLabel', currentLocale.code)}
li #{translate('verifyCredentialsErrorLabel', currentLocale.code)}
li #{translate('stackTraceErrorLabel', currentLocale.code)}
if error.response
h2 Error
h2 #{translate('errorLabel', currentLocale.code)}
pre.error__stack-trace
code.shell #{helpers.dump(error.response.data)}
h2 Stack trace
h2 #{translate('stackTraceLabel', currentLocale.code)}
pre.error__stack-trace
code.shell #{error.stack}

View File

@@ -13,22 +13,22 @@ block content
table
tbody
tr
th Company:
th #{translate('companyLabel', currentLocale.code)}:
td Contentful GmbH
tr
th Office Berlin:
th #{translate('officeLabel', currentLocale.code)}:
td
| Ritterstr. 12-14
br
| 10969 Berlin
br
| Germany
| #{translate('germanyLabel', currentLocale.code)}
tr
th Registration Court:
th #{translate('registrationCourtLabel', currentLocale.code)}:
td Berlin-Charlottenburg HRB 155607 B
tr
th Managing Director:
th #{translate('managingDirectorLabel', currentLocale.code)}:
td Sascha Konietzke
tr
th VAT Number:
th #{translate('vatNumberLabel', currentLocale.code)}:
td DE275148225

View File

@@ -1,20 +1,20 @@
doctype html
html
head
title= helpers.formatMetaTitle(title)
title= helpers.formatMetaTitle(title, currentLocale.code)
link(rel='stylesheet', href='/stylesheets/style.css')
meta(name='viewport' content='width=device-width, initial-scale=1')
meta(name='description' content='This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.')
meta(name='twitter:card' content='This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.')
meta(property='og:title' content=helpers.formatMetaTitle(title))
meta(name='description' content=translate('metaDescription', currentLocale.code))
meta(name='twitter:card' content=translate('metaTwitterCard', currentLocale.code))
meta(property='og:title' content=helpers.formatMetaTitle(title, currentLocale.code))
meta(property='og:type' content='article')
meta(property='og:url' content=`http://contentful-example-app.herokuapp.com${currentPath}`)
meta(property='og:image' content='http://contentful-example-app.herokuapp.com/og-image.jpg')
meta(property='og:image:type' content='image/jpeg')
meta(property='og:image:width' content='1200')
meta(property='og:image:height' content='1200')
meta(property='og:image:alt' content='This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.')
meta(property='og:description' content='This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.')
meta(property='og:image:alt' content=translate('metaImageAlt', currentLocale.code))
meta(property='og:description' content=translate('metaImageDescription', currentLocale.code))
link(rel='apple-touch-icon' sizes='120x120' href='/apple-touch-icon.png')
link(rel='icon' type='image/png' sizes='32x32' href='/favicon-32x32.png')
link(rel='icon' type='image/png' sizes='16x16' href='/favicon-16x16.png')
@@ -31,55 +31,55 @@ html
a#about-this-modal-trigger(href='#').header__title
svg.course__overview-icon
use(xlink:href='/icons/icons.svg#info')
span What is this example app?
span #{translate('whatIsThisApp', currentLocale.code)}
.header__upper-second
.header__upper-copy
a.header__upper-link(href='https://images.contentful.com/82t39nctsu20/1JOkYZnY8YG0w88ImweME2/c8aef71dfe1ea633e16e17d99379416c/Github-repo_2x__1_.png' target='_blank' rel='noopener')
svg.header__upper-icon
use(xlink:href='/icons/icons.svg#github')
| View on Github
| #{translate('viewOnGithub', currentLocale.code)}
.header__controls
.header__controls_group
form(action='' method='get')
.header__controls_label API: #{currentApi.label}
.header__controls_label API: #{currentApi.label} #{translate(`apiLabelHelp${currentApi.id}`, currentLocale.code)}
.header__controls_dropdown
.header__controls_help_text View the published or draft content by simply switching between the Deliver and Preview APIs.
.header__controls_help_text #{translate('apiSwitcherHelp', currentLocale.code)}
button.header__controls_button(
type='submit'
name='api'
value='cda'
class=`${currentApi.id === 'cda' ? 'header__controls_button--active' : ''}`
) Delivery (published content)
) Delivery #{translate('apiLabelHelpcda', currentLocale.code)}
button.header__controls_button(
type='submit'
name='api'
value='cpa'
class=`${currentApi.id === 'cpa' ? 'header__controls_button--active' : ''}`
) Preview (draft content)
) Preview #{translate('apiLabelHelpcpa', currentLocale.code)}
input(type='hidden' name='locale' value=currentLocale.code)
.header__controls_group
form(action='' method='get')
input(type='hidden' name='api' value=currentApi.id)
.header__controls_label Locale: #{currentLocale.name}
.header__controls_label #{translate('locale', currentLocale.code)}: #{currentLocale.name}
.header__controls_dropdown
.header__controls_help_text Working with multiple languages? You can query the Content Delivery API for a specific locale.
.header__controls_help_text #{translate('localeQuestion', currentLocale.code)}
each locale in locales
button.header__controls_button(type='submit' name='locale' value=locale.code class=`${locale.code === currentLocale.code ? 'header__controls_button--active' : ''}`)= `${locale.name} (${locale.code})`
.header__upper-menu
a(href=`/settings${queryString}` class=(currentPath.startsWith('/settings') ? 'active' : '')) Settings
a(href=`/settings${queryString}` class=(currentPath.startsWith('/settings') ? 'active' : '')) #{translate('settingsLabel', currentLocale.code)}
.header__lower-wrapper
.header__lower.layout-centered
.header__logo
a.header__logo-link(href=`/${queryString}`)
img(src='/images/logo-node.svg' alt='Contentful Example App')
img(src='/images/logo-node.svg' alt=translate('logoAlt', currentLocale.code))
nav.header__navigation.main-navigation
ul
li
a(href=`/${queryString}` class=(currentPath === '/'? 'active' : '')) Home
a(href=`/${queryString}` class=(currentPath === '/'? 'active' : '')) #{translate('homeLabel', currentLocale.code)}
li
a(href=`/courses${queryString}` class=(currentPath.startsWith('/courses') ? 'active' : '') ) Courses
a(href=`/courses${queryString}` class=(currentPath.startsWith('/courses') ? 'active' : '') ) #{translate('coursesLabel', currentLocale.code)}
.main__content
block content
@@ -90,9 +90,9 @@ html
nav.footer__navigation.main-navigation
ul
li
a(href=`/${queryString}` class=(currentPath === '/'? 'active' : '')) Home
a(href=`/${queryString}` class=(currentPath === '/'? 'active' : '')) #{translate('homeLabel', currentLocale.code)}
li
a(href=`/courses${queryString}` class=(currentPath.startsWith('/courses') ? 'active' : '') ) Courses
a(href=`/courses${queryString}` class=(currentPath.startsWith('/courses') ? 'active' : '') ) #{translate('coursesLabel', currentLocale.code)}
.footer__apps
//- a(href='#')
//- img(src='/images/badge-app-store.svg')
@@ -103,12 +103,12 @@ html
a(href='https://www.contentful.com/' target='_blank' rel='noopener')
img.footer__disclaimer-logo(src='/images/contentful-logo.svg')
p.footer__disclaimer-text
| Powered by Contentful. This website and the materials found on it are for demo purposes. You can use this to preview the content created on your Contentful account.&nbsp;
a(href='https://images.contentful.com/82t39nctsu20/1JOkYZnY8YG0w88ImweME2/c8aef71dfe1ea633e16e17d99379416c/Github-repo_2x__1_.png' target='_blank' rel='noopener') View on Github
| #{translate('footerDisclaimer', currentLocale.code)}&nbsp;
a(href='https://images.contentful.com/82t39nctsu20/1JOkYZnY8YG0w88ImweME2/c8aef71dfe1ea633e16e17d99379416c/Github-repo_2x__1_.png' target='_blank' rel='noopener') #{translate('viewOnGithub', currentLocale.code)}
| .&nbsp;
a(href=`/imprint${queryString}` ) Imprint
a(href=`/imprint${queryString}` ) #{translate('imprintLabel', currentLocale.code)}
| .&nbsp;
a(href=`https://www.contentful.com/contact/` ) Contact us
a(href=`https://www.contentful.com/contact/` ) #{translate('contactUsLabel', currentLocale.code)}
| .
.footer__social
p
@@ -124,15 +124,15 @@ html
section.modal#about-this-modal
.modal__overlay.close
.modal__wrapper
h1.modal__title A referenceable example for developers using Contentful
h1.modal__title #{translate('modalTitle', currentLocale.code)}
.modal__content
p This is The Example App, an application built to serve you as a reference while building your own applications using Contentful. This app is an online learning platform which teaches you how Contentful was used to build this app (so meta)!
p #{translate('modalIntro', currentLocale.code)}
p
| If you prefer to start by getting your hands dirty with code, check out this app on&nbsp;
| #{translate('modalCodeIntro', currentLocale.code)}&nbsp;
a(href='https://images.contentful.com/82t39nctsu20/1JOkYZnY8YG0w88ImweME2/c8aef71dfe1ea633e16e17d99379416c/Github-repo_2x__1_.png' target='_blank' rel='noopener') Github
| .
.modal__cta-wrapper
a(href='#').modal__cta.close Ok, got it.
a(href='#').modal__cta.close #{translate('modalCTALabel', currentLocale.code)}
.modal__close-wrapper
a(href='#').modal__close-button.close
svg

View File

@@ -13,5 +13,4 @@ mixin courseCard(course = {fields: {title: '', description: '', categories: [],
+entryState(course)
p.course-card__description= course.fields.shortDescription
.course-card__link-wrapper
a.course-card__link(href=`/courses/${course.fields.slug}${queryString}`) view course
a.course-card__link(href=`/courses/${course.fields.slug}${queryString}`) #{translate("viewCourseLabel", currentLocale.code)}

View File

@@ -15,4 +15,4 @@ mixin editorialFeatures(entry)
.editorial-features__hint-wrapper
svg.editorial-features__hint-icon
use(xlink:href='/icons/icons.svg#info')
.editorial-features__hint-message Edit this entry in our web app. You have to be logged in and have access to the connected space to use this feature.
.editorial-features__hint-message #{translate('editorialFeaturesHint', currentLocale.code)}

View File

@@ -1,5 +1,5 @@
mixin entryState(entry)
if entry.draft
.pill.pill--draft draft
.pill.pill--draft #{translate('draftLabel', currentLocale.code)}
if entry.pendingChanges
.pill.pill--pending-changes pending changes
.pill.pill--pending-changes #{translate('pendingChangesLabel', currentLocale.code)}

View File

@@ -20,10 +20,10 @@ mixin lesson(lesson, course, nextLesson)
when 'lessonImage'
+lessonModuleImage(module)
else
h2 ⚠️ Invalid lesson module
h2 #{translate('lessonModuleErrorTitle', currentLocale.code)}
p
span Could not determine type of
span #{translate('lessonModuleErrorBody', currentLocale.code)}
strong #{module.sys.id}
if nextLesson
a.lesson__cta.cta(href=`/courses/${course.fields.slug}/lessons/${nextLesson.fields.slug}${queryString}`) View next lesson
a.lesson__cta.cta(href=`/courses/${course.fields.slug}/lessons/${nextLesson.fields.slug}${queryString}`) #{translate('nextLessonLabel', currentLocale.code)}

View File

@@ -3,4 +3,4 @@ mixin lessonModuleImage(module)
if module.fields.image && module.fields.image.fields.file && module.fields.image.fields.file.url
img.lesson-module-image__image(src=module.fields.image.fields.file.url alt=module.fields.image.fields.title)
else
h3 ⚠️ Image missing
h3 #{translate('imageErrorTitle', currentLocale.code)}

View File

@@ -14,4 +14,4 @@ mixin moduleHighlightedCourse(module, course)
.module-higlighted-course__description-wrapper
p !{helpers.markdown(course.fields.shortDescription)}
.module-higlighted-course__link-wrapper
a.module-higlighted-course__link(href=`/courses/${course.fields.slug}${queryString}`) view course
a.module-higlighted-course__link(href=`/courses/${course.fields.slug}${queryString}`) #{translate('viewCourseLabel', currentLocale.code)}

View File

@@ -13,60 +13,60 @@ block content
.layout-centered-small
+breadcrumb
h1= title
p To query and get content using the APIs, client applications need to authenticate with both the Space ID and an access token.
p #{translate('settingsIntroLabel', currentLocale.code)}
if success
.status-block.status-block--success
svg.status-block__icon.status-block__icon--success
use(xlink:href='/icons/icons.svg#success')
.status-block__content
.status-block__title Changes saved successfully!
.status-block__title #{translate('changesSavedLabel', currentLocale.code)}
if hasErrors
.status-block.status-block--error
svg.status-block__icon.status-block__icon--error
use(xlink:href='/icons/icons.svg#error')
.status-block__content
.status-block__title Error occurred
.status-block__message Some errors occurred. Please check the error messages next to the fields.
.status-block__title #{translate('errorOcurredTitleLabel', currentLocale.code)}
.status-block__message #{translate('errorOcurredMessageLabel', currentLocale.code)}
if space && !hasErrors
.status-block.status-block--info
svg.status-block__icon.status-block__icon--info
use(xlink:href='/icons/icons.svg#info')
.status-block__content
.status-block__message Connected to space “#{space.name}”
.status-block__message #{translate('connectedToSpaceLabel', currentLocale.code)} “#{space.name}”
form(action=`/settings` method="POST" class="form")
.form-item
label(for="input-space-id") Space ID
label(for="input-space-id") #{translate('spaceIdLabel', currentLocale.code)}
input(type="text" name="spaceId" id="input-space-id" value=settings.spaceId)
if 'spaceId' in errors
+renderErrors(errors.spaceId)
.form-item__help-text The Space ID is a unique identifier for your space.
.form-item__help-text #{translate('spaceIdHelpText', currentLocale.code)}
.form-item
label(for="input-delivery-token") Content Delivery API - access token
label(for="input-delivery-token") Content Delivery API - #{translate('accessTokenLabel', currentLocale.code)}
input(type="text" name="deliveryToken" id="input-delivery-token" value=settings.deliveryToken)
if 'deliveryToken' in errors
+renderErrors(errors.deliveryToken)
.form-item__help-text
| View published content using this API.&nbsp;
| #{translate('contentDeliveryApiHelpText', currentLocale.code)}&nbsp;
a(href='https://www.contentful.com/developers/docs/references/content-delivery-api/' target='_blank' rel='noopener') Content Delivery API.
.form-item
label(for="input-preview-token") Content Preview API - access token
label(for="input-preview-token") Content Preview API - #{translate('accessTokenLabel', currentLocale.code)}
input(type="text" name="previewToken" id="input-preview-token" value=settings.previewToken)
if 'previewToken' in errors
+renderErrors(errors.previewToken)
.form-item__help-text
| Preview unpublished content using this API (i.e. content with “Draft” status).&nbsp;
| #{translate('contentPreviewApiHelpText', currentLocale.code)}&nbsp;
a(href='https://www.contentful.com/developers/docs/references/content-preview-api/' target='_blank' rel='noopener') Content Preview API.
.form-item
input(type="checkbox" name="editorialFeatures" id="input-editorial-features" checked=settings.editorialFeatures)
label(for="input-editorial-features") Enable editorial features
.form-item__help-text Enable to display an edit link and other contextual helpers for authors. You need to have access to the connected space to make this work.
label(for="input-editorial-features") #{translate('enableEditorialFeaturesLabel', currentLocale.code)}
.form-item__help-text #{translate('enableEditorialFeaturesHelpText', currentLocale.code)}
.form-item
input.cta(type="submit" value="Save settings")
input.cta(type="submit" value=translate('saveSettingsButtonLabel', currentLocale.code))