Skip to content

Commit

Permalink
feat: add internationalization support
Browse files Browse the repository at this point in the history
  • Loading branch information
mistakia committed Feb 19, 2024
1 parent fa11e6e commit f1d32cb
Show file tree
Hide file tree
Showing 63 changed files with 3,873 additions and 3,050 deletions.
43 changes: 40 additions & 3 deletions api/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import serveStatic from 'serve-static'
import cors from 'cors'
import favicon from 'express-favicon'
import robots from 'express-robots-txt'
import { slowDown } from 'express-slow-down'

import * as config from '#config'
import * as routes from '#api/routes/index.mjs'
Expand Down Expand Up @@ -70,6 +71,9 @@ api.use((req, res, next) => {
const resourcesPath = path.join(__dirname, '..', 'resources')
api.use('/resources', serveStatic(resourcesPath))

const localesPath = path.join(__dirname, '..', 'locales')
api.use('/locales', serveStatic(localesPath))

const dataPath = path.join(__dirname, '..', 'data')
api.use('/data', serveStatic(dataPath))

Expand All @@ -94,9 +98,42 @@ api.use('/api/representatives', routes.representatives)
api.use('/api/weight', routes.weight)

const docsPath = path.join(__dirname, '..', 'docs')
api.use('/api/docs', serveStatic(docsPath))
api.get('/api/docs/*', (req, res) => {
res.status(404).send('Not found')

const speedLimiter = slowDown({
windowMs: 10 * 60 * 1000, // 10 minutes
delayAfter: 50, // allow 50 requests per 10 minutes, then...
delayMs: 500, // begin adding 500ms of delay per request above 50:
maxDelayMs: 20000 // maximum delay of 20 seconds
})

api.use('/api/docs', speedLimiter, serveStatic(docsPath))
api.use('/api/docs/en', speedLimiter, serveStatic(docsPath))
api.get('/api/docs/:locale/*', speedLimiter, async (req, res) => {
const { locale } = req.params
const doc_id = req.params[0] // Capture the rest of the path as doc_id
const localized_doc_path = path.join(docsPath, locale, `${doc_id}.md`)
const default_doc_path = path.join(docsPath, 'en', `${doc_id}.md`)

// check if paths are under the docs directory
if (
!localized_doc_path.startsWith(docsPath) ||
!default_doc_path.startsWith(docsPath)
) {
return res.status(403).send('Forbidden')
}

try {
if (fs.existsSync(localized_doc_path)) {
return res.sendFile(localized_doc_path)
} else if (fs.existsSync(default_doc_path)) {
return res.redirect(`/api/docs/en/${doc_id}`)
} else {
return res.status(404).send('Document not found')
}
} catch (error) {
console.error(error)
return res.status(500).send('Internal Server Error')
}
})

Check failure

Code scanning / CodeQL

Missing rate limiting High

This route handler performs
a file system access
, but is not rate-limited.
This route handler performs
a file system access
, but is not rate-limited.
This route handler performs
a file system access
, but is not rate-limited.

api.use('/api/*', (err, req, res, next) => {
Expand Down
280 changes: 280 additions & 0 deletions locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
{
"common": {
"blocks": "Blocks",
"total": "Total",
"min": "Min",
"max": "Max",
"delegator": "Delegator",
"balance": "Balance",
"percent_of_total": "% of Total",
"collapse": "Collapse",
"show_more": "Show {{count}} more"
},
"menu": {
"introduction": "Introduction",
"overview": "Overview",
"advantages": "Advantages",
"how_it_works": "How it works",
"why_it_matters": "Why it matters",
"misconceptions": "Misconceptions",
"investment_thesis": "Investment thesis",
"history": "History",
"faqs": "FAQs",
"guides": "Guides",
"basics": "Basics",
"storing": "Storing",
"acquiring": "Acquiring",
"choosing_a_rep": "Choosing a Rep",
"using": "Using",
"account_setup": "Account Setup",
"privacy": "Privacy",
"best_practices": "Best Practices",
"learn": "Learn",
"design": "Design",
"security": "Security",
"attack_vectors": "Attack Vectors",
"challenges": "Challenges",
"glossary": "Glossary",
"get_support": "Get Support",
"developers": "Developers",
"getting_started": "Getting Started",
"integrations": "Integrations",
"running_a_node": "Running a node",
"documentation": "Documentation",
"protocol": "Protocol",
"developer_discussions": "Developer Discussions",
"get_involved": "Get Involved",
"planning": "Planning 👾",
"contribution_guide": "Contribution Guide",
"communities": "Communities",
"stats": "Stats",
"representatives": "Representatives",
"telemetry": "Telemetry",
"ledger": "Ledger",
"topics": "Topics"
},
"posts": {
"top": "Top",
"nano_foundation": "Nano Foundation",
"trending": "Trending"
},
"network": {
"statsTitle": "Network Stats",
"confirmations": "Confirmations (24h)",
"settlement": "Settlement (24h)",
"txFees": "Tx Fees (24h)",
"txThroughput": "Tx Throughput",
"txSpeed": "Tx Speed",
"txBacklog": "Tx Backlog",
"onlineStake": "Online Stake",
"principalReps": "Principal Reps",
"totalReps": "Total Reps (24h)",
"peers": "Peers",
"repsToConfirm": "Reps to Confirm",
"repsToCensor": "Reps to Censor or Stall",
"energyUsage": "Energy Usage (TDP) (24h)",
"nanoTicker": "NanoTicker",
"prText": "as observed across the networks principal representatives: voting nodes with more than 0.1% of the online voting weight delegated to them",
"confirmationsText": "Total number of transactions confirmed by the network over the last 24 hours",
"settlementText": "Total amount of value settled by the network over the last 24 hours",
"throughputText": "Median number of transactions confirmed per second in the last minute",
"speedText": "Time in milliseconds for a test transaction to get confirmed",
"backlogText": "Median number of transactions waiting to be confirmed",
"stakeText": "Percentage of delegated Nano weight actively participating in voting",
"confirmText": "The minimum number of representatives needed to confirm transactions",
"censorText": "The minimum number of representatives needed to censor transactions or stall the network",
"feeText": "The Nano network operates without fees",
"energyText": "Estimated live network CPU energy usage of Principle Representatives based on collected CPU model info. The estimate is based on CPU TDP, which is the average power, in watts, the processor dissipates when operating at base frequency with all cores active under manufacture-defined, high-complexity workload"
},
"representative_network": {
"provider": "Provider",
"isp": "ISP",
"country": "Country",
"city": "City",
"network": "Network"
},
"github_events": {
"action": {
"commented_on_commit": "commented on commit",
"created": "created {{action}}",
"deleted": "deleted {{action}}",
"forked": "forked",
"commented_on_issue": "commented on issue",
"issue_action": "{{action}} issue",
"made_public": "made public",
"added_member": "added member",
"sponsorship_started": "sponsorship started",
"pr_action": "{{action}} pr",
"pr_review": "pr review {{title}}",
"commented_on_pr_review": "commented on pr review",
"pushed_commit": "pushed commit to {{ref}}",
"published_release": "published release",
"watching_repo": "watching repo"
},
"events_title": "Development Events"
},
"representative_alerts": {
"tooltip": {
"offline": "Representative has stopped voting and appears offline.",
"behind": "Representative has fallen behind or is bootstrapping. The cutoff is a cemented count beyond the 95th percentile. (via telemetry)",
"overweight": "Representative has beyond 3M Nano voting weight. Delegators should consider distributing the weight to improve the network's resilience and value.",
"low_uptime": "Representative has been offline more than 25% in the last 28 days."
},
"table_header": {
"representative": "Representative",
"issue": "Issue",
"last_online": "Last Online",
"weight": "Weight",
"percent_online_weight": "% Online Weight",
"behind": "Behind"
},
"type": {
"offline": "Offline",
"behind": "Behind",
"overweight": "Overweight",
"low_uptime": "Low Uptime"
},
"actions": {
"collapse": "Collapse",
"show_more": "Show more"
}
},
"representative_uptime": {
"current_status": "Current Status",
"2w_uptime": "2W Uptime",
"2m_uptime": "2M Uptime",
"3m_uptime": "3M Uptime",
"online": "Online",
"offline": "Offline",
"warning": "Warning",
"operational": "Operational",
"down": "Down",
"up_for": "Up for",
"down_for": "Down for"
},
"doc": {
"section_link_copied": "Section link copied",
"not_found_404": "404",
"document_not_found": "Document (or Account) not found",
"contributors": "Contributor",
"help_out": "Help out",
"updated_by": "updated by",
"edit_page": "Edit Page"
},
"roadmap": {
"seo": {
"title": "Roadmap",
"description": "Nano development & community roadmap",
"tags": [
"roadmap",
"nano",
"future",
"release",
"design",
"tasks",
"discussions",
"community",
"ambassadors",
"managers"
]
},
"header": {
"title": "Planning",
"subtitle": "Community objectives"
}
},
"accountBlocksSummary": {
"sending_account": "Sending Account",
"receiving_account": "Receiving Account",
"representative_account": "Representative Account",
"transactions": "TXs",
"total": "Total",
"maxAmount": "Max Amount",
"minAmount": "Min Amount",
"firstTimestamp": "First Timestamp",
"lastTimestamp": "Last Timestamp",
"noRecords": "No Records",
"showingTop10": "Showing top 10 accounts by total descending"
},
"accountMeta": {
"fundingAccount": "Funding Account",
"fundingTimestamp": "Funding Timestamp",
"openTimestamp": "Open Timestamp",
"openingBalance": "Opening Balance",
"receivableBalance": "Receivable Balance",
"version": "Version",
"height": "Height",
"lastModified": "Last Modified",
"accountInfo": "Account Info"
},
"blockType": {
"epoch": "Epoch",
"send": "Send",
"receive": "Receive",
"change": "Change",
"open": "Open"
},
"blockStatus": {
"confirmed": "Confirmed",
"unconfirmed": "Unconfirmed"
},
"blockInfo": {
"status": "Status",
"operation": "Operation",
"timestamp": "Timestamp",
"blockAccount": "Block Account"
},
"ledger": {
"description": "Description",
"addresses": {
"totalNumber": "The total number of active, new, and reused addresses used per day.",
"activeDetail": "Active shows the number of unique addresses used. New shows the number of addresses created. Reused shows the number of addresses used that were created on a previous day.",
"activeStats": "Active Address Stats",
"newStats": "New Address Stats"
},
"amounts": {
"totalNumber": "The number of confirmed send-type blocks per day where the amount in the block is in a given range (in Nano)"
},
"blocks": {
"description": "The number of blocks confirmed per day.",
"total": "Total Block Stats",
"send": "Send Block Stats",
"receive": "Receive Block Stats",
"open": "Open Block Stats",
"change": "Change Block Stats"
},
"usd_transferred": {
"desc_1": "The total amount of value transferred (in USD) per day.",
"desc_2": "Based on the daily closing price of Nano/USD and the total amount of Nano transferred that day.",
"usd_transferred": "USD Transferred",
"usd_transferred_stats": "USD Transferred Stats"
},
"volume": {
"description": "The total amount sent (in Nano) and total amount of voting weight changed per day.",
"send_stats": "Send Stats",
"change_stats": "Change Stats"
}
},
"delegators": {
"showing_top_delegators": "Showing top 100 delegators with a minimum balance of 1 Nano."
},
"representative_info": {
"last_seen": "Last Seen",
"first_seen": "First Seen",
"weight_represented": "Weight Represented"
},
"representative_telemetry": {
"telemetry": "Telemetry",
"peers": "Peers",
"port": "Port",
"version": "Version",
"bandwidth_limit": "Bandwidth Limit",
"blocks": "Blocks",
"blocks_diff": "Blocks Diff",
"conf": "Conf.",
"conf_diff": "Conf. Diff",
"unchecked": "Unchecked",
"telemetry_timestamp": "Telemetry Timestamp",
"unlimited": "Unlimited"
}
}
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,12 @@
"express-favicon": "^2.0.4",
"express-jwt": "^8.4.1",
"express-robots-txt": "^1.0.0",
"express-slow-down": "^2.0.1",
"fetch-cheerio-object": "^1.3.0",
"front-matter": "^4.0.2",
"fs-extra": "^11.1.1",
"i18next": "^23.8.2",
"i18next-http-backend": "^2.4.3",
"jsonwebtoken": "^9.0.1",
"knex": "^0.95.15",
"markdown-it": "^12.3.2",
Expand All @@ -114,6 +117,7 @@
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-helmet": "^6.1.0",
"react-i18next": "^14.0.5",
"react-redux": "^7.2.9",
"react-router": "^5.3.4",
"redux-saga": "^1.2.3",
Expand All @@ -140,6 +144,7 @@
"compression-webpack-plugin": "^10.0.0",
"concurrently": "^8.2.0",
"copy-text-to-clipboard": "^3.2.0",
"copy-webpack-plugin": "^12.0.2",
"cross-env": "^7.0.3",
"css-loader": "6.8.1",
"deepmerge": "4.3.1",
Expand Down
11 changes: 8 additions & 3 deletions src/core/api/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@ export const api = {
const url = `${API_URL}/posts/${id}?${queryString.stringify(params)}`
return { url }
},
getDoc({ id }) {
const url = `${API_URL}/docs${id}.md`
return { url }
getDoc({ id, locale = 'en' }) {
if (locale === 'en') {
const url = `${API_URL}/docs${id}.md`
return { url }
} else {
const url = `${API_URL}/docs/${locale}/${id}.md`
return { url }
}
},
getLabelDoc({ id }) {
const url = `${API_URL}/docs${id}.md`
Expand Down

0 comments on commit f1d32cb

Please sign in to comment.