Localization refactoring:
* the front-end now use global constants, based on the translation key * build-client.js use the ESBuild "define" directive to replace these globals at compile time, by the english value * build:client must now be called after build:languages * moving the loadLoc and loc backend functions in a separate lib
This commit is contained in:
parent
7285c8b0a8
commit
f73ccbbf7e
@ -1,5 +1,6 @@
|
||||
const path = require('path')
|
||||
const esbuild = require('esbuild')
|
||||
const fs = require('fs')
|
||||
|
||||
const packagejson = require('./package.json')
|
||||
const sourcemap = process.env.NODE_ENV === 'dev' ? 'inline' : false
|
||||
@ -11,15 +12,40 @@ const clientFiles = [
|
||||
'admin-plugin-client-plugin'
|
||||
]
|
||||
|
||||
function loadLocs() {
|
||||
// Loading english strings, so we can inject them as constants.
|
||||
const refFile = path.resolve(__dirname, 'dist', 'languages', 'en.reference.json')
|
||||
if (!fs.existsSync(refFile)) {
|
||||
throw new Error('Missing english reference file, please run "npm run build:languages" before building the client')
|
||||
}
|
||||
const english = require(refFile)
|
||||
|
||||
// Reading client/@types/global.d.ts, to have a list of needed localized strings.
|
||||
const r = {}
|
||||
const globalFile = path.resolve(__dirname, 'client', '@types', 'global.d.ts')
|
||||
const globalFileContent = '' + fs.readFileSync(globalFile)
|
||||
const matches = globalFileContent.matchAll(/^declare const LOC_(\w+)\b/gm)
|
||||
for (const match of matches) {
|
||||
const key = match[1].toLowerCase()
|
||||
if (!(key in english) || (typeof english[key] !== 'string')) {
|
||||
throw new Error('Missing english string key=' + key)
|
||||
}
|
||||
r['LOC_' + match[1]] = JSON.stringify(english[key])
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
const define = Object.assign({
|
||||
PLUGIN_CHAT_PACKAGE_NAME: JSON.stringify(packagejson.name),
|
||||
PLUGIN_CHAT_SHORT_NAME: JSON.stringify(packagejson.name.replace(/^peertube-plugin-/, ''))
|
||||
}, loadLocs())
|
||||
|
||||
const configs = clientFiles.map(f => ({
|
||||
entryPoints: [ path.resolve(__dirname, 'client', f + '.ts') ],
|
||||
alias: {
|
||||
'shared': path.resolve(__dirname, 'shared/')
|
||||
},
|
||||
define: {
|
||||
PLUGIN_CHAT_PACKAGE_NAME: JSON.stringify(packagejson.name),
|
||||
PLUGIN_CHAT_SHORT_NAME: JSON.stringify(packagejson.name.replace(/^peertube-plugin-/, ''))
|
||||
},
|
||||
define,
|
||||
bundle: true,
|
||||
minify: true,
|
||||
// FIXME: sourcemap:`true` does not work for now, because peertube does not serve static files.
|
||||
|
29
client/@types/global.d.ts
vendored
29
client/@types/global.d.ts
vendored
@ -1,2 +1,31 @@
|
||||
declare const PLUGIN_CHAT_PACKAGE_NAME: string
|
||||
declare const PLUGIN_CHAT_SHORT_NAME: string
|
||||
|
||||
// Constants that begins with "LOC_" are loaded by build-client.js, reading the english locale file.
|
||||
// See the online documentation: https://johnxlivingston.github.io/peertube-plugin-livechat/contributing/translate/
|
||||
declare const LOC_OPEN_CHAT: string
|
||||
declare const LOC_OPEN_CHAT_NEW_WINDOW: string
|
||||
declare const LOC_CLOSE_CHAT: string
|
||||
declare const LOC_USE_CHAT: string
|
||||
declare const LOC_USE_CHAT_HELP: string
|
||||
declare const LOC_SHARE_CHAT_LINK: string
|
||||
declare const LOC_READ_ONLY: string
|
||||
declare const LOC_SHOW_SCROLLBARR: string
|
||||
declare const LOC_TRANSPARENT_BACKGROUND: string
|
||||
declare const LOC_TIPS_FOR_STREAMERS: string
|
||||
declare const LOC_COPY: string
|
||||
declare const LOC_LINK_COPIED: string
|
||||
declare const LOC_ERROR: string
|
||||
declare const LOC_OPEN: string
|
||||
declare const LOC_USE_CURRENT_THEME_COLOR: string
|
||||
declare const LOC_GENERATE_IFRAME: string
|
||||
declare const LOC_CHAT_FOR_LIVE_STREAM: string
|
||||
declare const LOC_ROOM_NAME: string
|
||||
declare const LOC_ROOM_DESCRIPTION: string
|
||||
declare const LOC_NOT_FOUND: string
|
||||
declare const LOC_VIDEO: string
|
||||
declare const LOC_CHANNEL: string
|
||||
declare const LOC_LAST_ACTIVITY: string
|
||||
declare const LOC_WEB: string
|
||||
declare const LOC_CONNECT_USING_XMPP: string
|
||||
declare const LOC_CONNECT_USING_XMPP_HELP: string
|
||||
|
@ -100,12 +100,12 @@ function register ({ registerHook, registerSettingsScript, peertubeHelpers }: Re
|
||||
table.classList.add('peertube-plugin-livechat-prosody-list-rooms')
|
||||
container.append(table)
|
||||
const labels: any = {
|
||||
RoomName: await peertubeHelpers.translate('Room name'),
|
||||
RoomDescription: await peertubeHelpers.translate('Room description'),
|
||||
NotFound: await peertubeHelpers.translate('Not found'),
|
||||
Video: await peertubeHelpers.translate('Video'),
|
||||
Channel: await peertubeHelpers.translate('Channel'),
|
||||
LastActivity: await peertubeHelpers.translate('Last activity')
|
||||
RoomName: await peertubeHelpers.translate(LOC_ROOM_NAME),
|
||||
RoomDescription: await peertubeHelpers.translate(LOC_ROOM_DESCRIPTION),
|
||||
NotFound: await peertubeHelpers.translate(LOC_NOT_FOUND),
|
||||
Video: await peertubeHelpers.translate(LOC_VIDEO),
|
||||
Channel: await peertubeHelpers.translate(LOC_CHANNEL),
|
||||
LastActivity: await peertubeHelpers.translate(LOC_LAST_ACTIVITY)
|
||||
}
|
||||
|
||||
const titleLineEl = document.createElement('tr')
|
||||
|
@ -22,8 +22,8 @@ async function register ({ peertubeHelpers, registerHook, registerVideoField }:
|
||||
})
|
||||
|
||||
const [label, description, settings] = await Promise.all([
|
||||
peertubeHelpers.translate('Use chat'),
|
||||
peertubeHelpers.translate('If enabled, there will be a chat next to the video.'),
|
||||
peertubeHelpers.translate(LOC_USE_CHAT),
|
||||
peertubeHelpers.translate(LOC_USE_CHAT_HELP),
|
||||
peertubeHelpers.getSettings()
|
||||
])
|
||||
const webchatFieldOptions: RegisterClientFormFieldOptions = {
|
||||
|
@ -76,10 +76,10 @@ function register (registerOptions: RegisterClientOptions): void {
|
||||
const p = new Promise<void>((resolve, reject) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
Promise.all([
|
||||
peertubeHelpers.translate('Open chat'),
|
||||
peertubeHelpers.translate('Open chat in a new window'),
|
||||
peertubeHelpers.translate('Close chat'),
|
||||
peertubeHelpers.translate('Share chat link')
|
||||
peertubeHelpers.translate(LOC_OPEN_CHAT),
|
||||
peertubeHelpers.translate(LOC_OPEN_CHAT_NEW_WINDOW),
|
||||
peertubeHelpers.translate(LOC_CLOSE_CHAT),
|
||||
peertubeHelpers.translate(LOC_SHARE_CHAT_LINK)
|
||||
]).then(labels => {
|
||||
const labelOpen = labels[0]
|
||||
const labelOpenBlank = labels[1]
|
||||
|
@ -40,23 +40,21 @@ async function shareChatUrl (registerOptions: RegisterClientOptions, settings: a
|
||||
labelGenerateIframe,
|
||||
labelChatFor
|
||||
] = await Promise.all([
|
||||
peertubeHelpers.translate('Share chat link'),
|
||||
peertubeHelpers.translate('Web'),
|
||||
peertubeHelpers.translate('Connect using XMPP'),
|
||||
// eslint-disable-next-line max-len
|
||||
peertubeHelpers.translate('You can connect to the room using an external XMPP account, and your favorite XMPP client.'),
|
||||
peertubeHelpers.translate('Read-only'),
|
||||
peertubeHelpers.translate('Show the scrollbar'),
|
||||
peertubeHelpers.translate('Transparent background (for stream integration, with OBS for example)'),
|
||||
// eslint-disable-next-line max-len
|
||||
peertubeHelpers.translate('Tips for streamers: To add the chat to your OBS, generate a read-only link and use it as a browser source.'),
|
||||
peertubeHelpers.translate('Copy'),
|
||||
peertubeHelpers.translate('Link copied'),
|
||||
peertubeHelpers.translate('Error'),
|
||||
peertubeHelpers.translate('Open'),
|
||||
peertubeHelpers.translate('Use current theme colors'),
|
||||
peertubeHelpers.translate('Generate an iframe to embed the chat in a website'),
|
||||
peertubeHelpers.translate('Chat for live stream:')
|
||||
peertubeHelpers.translate(LOC_SHARE_CHAT_LINK),
|
||||
peertubeHelpers.translate(LOC_WEB),
|
||||
peertubeHelpers.translate(LOC_CONNECT_USING_XMPP),
|
||||
peertubeHelpers.translate(LOC_CONNECT_USING_XMPP_HELP),
|
||||
peertubeHelpers.translate(LOC_READ_ONLY),
|
||||
peertubeHelpers.translate(LOC_SHOW_SCROLLBARR),
|
||||
peertubeHelpers.translate(LOC_TRANSPARENT_BACKGROUND),
|
||||
peertubeHelpers.translate(LOC_TIPS_FOR_STREAMERS),
|
||||
peertubeHelpers.translate(LOC_COPY),
|
||||
peertubeHelpers.translate(LOC_LINK_COPIED),
|
||||
peertubeHelpers.translate(LOC_ERROR),
|
||||
peertubeHelpers.translate(LOC_OPEN),
|
||||
peertubeHelpers.translate(LOC_USE_CURRENT_THEME_COLOR),
|
||||
peertubeHelpers.translate(LOC_GENERATE_IFRAME),
|
||||
peertubeHelpers.translate(LOC_CHAT_FOR_LIVE_STREAM)
|
||||
])
|
||||
|
||||
const defaultUri = getIframeUri(registerOptions, settings, video)
|
||||
|
@ -96,7 +96,7 @@
|
||||
"build:prosodymodules": "mkdir -p dist/server/prosody-modules && cp -r prosody-modules/* dist/server/prosody-modules/",
|
||||
"build:styles": "sass assets/styles:dist/assets/styles",
|
||||
"build:languages": "node ./build-languages.js",
|
||||
"build": "npm-run-all -s clean:light check:client:tsc -p build:converse build:prosody build:images build:avatars build:client build:server build:languages build:serverconverse build:prosodymodules build:styles",
|
||||
"build": "npm-run-all -s clean:light build:languages check:client:tsc -p build:converse build:prosody build:images build:avatars build:client build:server build:serverconverse build:prosodymodules build:styles",
|
||||
"lint": "npm-run-all -s lint:script lint:styles",
|
||||
"lint:script": "npx eslint --ext .js --ext .ts .",
|
||||
"lint:styles": "stylelint 'conversejs/**/*.scss' 'assets/styles/**/*.css'",
|
||||
|
43
server/lib/loc.ts
Normal file
43
server/lib/loc.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { resolve } from 'path'
|
||||
import { existsSync, promises as fsPromises } from 'fs'
|
||||
|
||||
const locContent: Map<string, string> = new Map<string, string>()
|
||||
|
||||
/**
|
||||
* Currently, the Peertube plugin system assumes that settings strings
|
||||
* are localized in english, and will be translated on front-end.
|
||||
* This system make it hard to have complex strings (with html, newlines, ...).
|
||||
* See https://github.com/Chocobozzz/PeerTube/issues/4523
|
||||
*
|
||||
* Waiting for a better solution, we implemented a custom solution:
|
||||
* - We are using keys to identify strings
|
||||
* - the `loc` function gets the english segment for the key
|
||||
* - the build-languages.js script builds all needed files.
|
||||
* @param key The key to translate
|
||||
*/
|
||||
function loc (key: string): string {
|
||||
return locContent.get(key) ?? key
|
||||
}
|
||||
|
||||
async function loadLoc (): Promise<void> {
|
||||
const filePath = resolve(__dirname, '..', '..', 'languages', 'en.reference.json')
|
||||
if (!existsSync(filePath)) {
|
||||
throw new Error(`File ${filePath} missing, can't load plugin loc strings`)
|
||||
}
|
||||
const content = await fsPromises.readFile(filePath, 'utf8')
|
||||
const json = JSON.parse(content ?? '{}')
|
||||
if (typeof json !== 'object') {
|
||||
throw new Error(`File ${filePath} invalid, can't load plugin loc strings`)
|
||||
}
|
||||
for (const k in json) {
|
||||
const v = json[k]
|
||||
if (typeof v === 'string') {
|
||||
locContent.set(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
loc,
|
||||
loadLoc
|
||||
}
|
@ -1,50 +1,11 @@
|
||||
import type { RegisterServerOptions } from '@peertube/peertube-types'
|
||||
import { ensureProsodyRunning } from './prosody/ctl'
|
||||
import type { ConverseJSTheme } from '../../shared/lib/types'
|
||||
import { existsSync, promises as fsPromises } from 'fs'
|
||||
import { resolve } from 'path'
|
||||
|
||||
const locContent: Map<string, string> = new Map<string, string>()
|
||||
|
||||
/**
|
||||
* Currently, the Peertube plugin system assumes that settings strings
|
||||
* are localized in english, and will be translated on front-end.
|
||||
* This system make it hard to have complex strings (with html, newlines, ...).
|
||||
* See https://github.com/Chocobozzz/PeerTube/issues/4523
|
||||
*
|
||||
* Waiting for a better solution, we implemented a custom solution:
|
||||
* - We are using keys to identify setting strings
|
||||
* - the `loc` function gets the english segment for the key
|
||||
* - the build-languages.js script builds all needed files.
|
||||
* @param key The key to translate
|
||||
*/
|
||||
function loc (key: string): string {
|
||||
return locContent.get(key) ?? key
|
||||
}
|
||||
|
||||
async function loadLoc (): Promise<void> {
|
||||
const filePath = resolve(__dirname, '..', '..', 'languages', 'en.reference.json')
|
||||
if (!existsSync(filePath)) {
|
||||
throw new Error(`File ${filePath} missing, can't load plugin settings`)
|
||||
}
|
||||
const content = await fsPromises.readFile(filePath, 'utf8')
|
||||
const json = JSON.parse(content ?? '{}')
|
||||
if (typeof json !== 'object') {
|
||||
throw new Error(`File ${filePath} invalid, can't load plugin settings`)
|
||||
}
|
||||
for (const k in json) {
|
||||
const v = json[k]
|
||||
if (typeof v === 'string') {
|
||||
locContent.set(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
import { loc } from './loc'
|
||||
|
||||
async function initSettings (options: RegisterServerOptions): Promise<void> {
|
||||
const { peertubeHelpers, registerSetting, settingsManager } = options
|
||||
|
||||
await loadLoc()
|
||||
|
||||
// ********** IMPORTANT NOTES
|
||||
registerSetting({
|
||||
type: 'html',
|
||||
|
@ -6,6 +6,7 @@ import { initRouters } from './lib/routers/index'
|
||||
import { initFederation } from './lib/federation/init'
|
||||
import { prepareProsody, ensureProsodyRunning, ensureProsodyNotRunning } from './lib/prosody/ctl'
|
||||
import { unloadDebugMode } from './lib/debug'
|
||||
import { loadLoc } from './lib/loc'
|
||||
import decache from 'decache'
|
||||
|
||||
// FIXME: Peertube unregister don't have any parameter.
|
||||
@ -20,6 +21,9 @@ async function register (options: RegisterServerOptions): Promise<any> {
|
||||
throw new Error('Your peertube version is not correct. This plugin is not compatible with Peertube < 3.2.0.')
|
||||
}
|
||||
|
||||
// First: load languages files, so we can localize strings.
|
||||
await loadLoc()
|
||||
|
||||
await migrateSettings(options)
|
||||
|
||||
await initSettings(options)
|
||||
|
@ -45,14 +45,38 @@ If you are working on new features, and need new strings, you can create them di
|
||||
The english version is mandatory. Start with it.
|
||||
|
||||
Each string is linked to a key (for example `use_chat`).
|
||||
Choose an explicit key in english.
|
||||
Choose an explicit key in english, lower case.
|
||||
|
||||
To use a string in front-end, you need (for now) to call `peertubeHelpers.translate` with the english string.
|
||||
This means we can't change english strings without updating the code.
|
||||
This is not optimal, but will change in a near future.
|
||||
If you have to test new strings without waiting for a Weblate merge, you can modify `languages/*.yml` files,
|
||||
but avoid to commit these changes (to minimize conflict risks).
|
||||
|
||||
For backend, for now the only file where there is localisation is
|
||||
`server/lib/settings.ts`. There is a `loc` function to call, passing as parameter the localisation key.
|
||||
### Use translations in front-end code
|
||||
|
||||
If you have to test new strings without waiting for a Weblate merge, you can modify `languages/*.yml` files, but avoid to commit these change
|
||||
(to minimize conflict risks).
|
||||
Before using a string in front-end, you need to declare a new constant in `client/@types/global.d.ts`.
|
||||
The constant name must:
|
||||
|
||||
* start with the prefix "LOC_"
|
||||
* use the string key, upper cased
|
||||
* you just have to declare its type, not its value
|
||||
|
||||
For example, to use "use_chat", you have to declare:
|
||||
|
||||
```typescript
|
||||
declare const LOC_USE_CHAT: string
|
||||
```
|
||||
|
||||
The `build-client.js` script will read the `client/@types/global.d.ts`,
|
||||
search for such constants, and load their values from the languages files.
|
||||
|
||||
Now, you can simply call `peertubeHelpers.translate(LOC_USE_CHAT)` in your code.
|
||||
|
||||
### Use translations in back-end code
|
||||
|
||||
In theory, the only parts of the backend code where you need localization is the
|
||||
settings declaration. Here we need to get english strings from the translation key.
|
||||
|
||||
Note: you should never need another language translation from backend code.
|
||||
Localization must be done on front-end.
|
||||
|
||||
There is a `lib/loc.ts` module providing a `loc()` function.
|
||||
Just pass it the key to have the english string: `loc('diagnostic')`'.
|
||||
|
@ -50,18 +50,39 @@ créez les directement dans Weblate.
|
||||
La version anglaise est obligatoire, commencez par celle-ci.
|
||||
|
||||
Chaque segment est lié à une clé (par exemple `use_chat`).
|
||||
Choisissez une clé en anglais, suffisamment explicite.
|
||||
|
||||
Pour utiliser un segment coté front-end, il faut (pour l'instant), appeler `peertubeHelpers.translate`
|
||||
avec la version anglaise du texte. Attention, cela veut-dire qu'il faut éviter de changer un segment anglais
|
||||
existant.
|
||||
Cette solution n'est pas optimale, mais devrais bientôt changer.
|
||||
|
||||
Coté backend, le seul endroit (pour l'instant) qui a besoin de localiser des choses, est la déclaration
|
||||
des settings du plugin.
|
||||
Il y a pour cela une fonction `loc` dédiée dans `server/lib/settings.ts` à appeler en lui fournissant
|
||||
la clé de la phrase à utiliser.
|
||||
Choisissez une clé en anglais, suffisamment explicite, et en minuscule.
|
||||
|
||||
Si vous avez besoin de tester vos localisations sans attendre la fusion venant de Weblate,
|
||||
vous pouvez modifier les fichiers `languages/*.yml`, mais évitez de les commit
|
||||
(pour minimiser le risque de conflits).
|
||||
|
||||
### Utiliser un segment dans le code front-end
|
||||
|
||||
Avant d'utiliser une chaîne en front-end, il faut déclarer une nouvelle constante dans `client/@types/global.d.ts`.
|
||||
La constante doit :
|
||||
|
||||
* commencer par le préfixe "LOC_"
|
||||
* utiliser la clé de la chaîne, en majuscule
|
||||
* vous ne devez déclarer que son type, pas sa valeur
|
||||
|
||||
Par exemple, pour utiliser "use_chat", vous devez déclarer :
|
||||
e, to use "use_chat", you have to declare:
|
||||
|
||||
```typescript
|
||||
declare const LOC_USE_CHAT: string
|
||||
```
|
||||
|
||||
Le script `build-client.js` va lire ce fichier `client/@types/global.d.ts`, chercher pour de telles constantes, et charger leurs valeurs depuis le fichier de langue.
|
||||
|
||||
Vous pouvez maintenant utiliser `peertubeHelpers.translate(LOC_USE_CHAT)` dans votre code.
|
||||
|
||||
### Utiliser un segment dans le code back-end
|
||||
|
||||
En théorie, les seules parties du code qui ont besoin de traductions sont les déclarations de paramètres.
|
||||
Ici on a besoin de récupérer les chaînes anglaises à partir des clés de traduction.
|
||||
|
||||
Note: vous ne devriez jamais avoir besoin d'autres langues que l'anglais pour le code backend.
|
||||
Les traductions doivent se faire coté front-end.
|
||||
|
||||
Il y a un module `lib/loc.ts` qui fourni une function `loc()`.
|
||||
Passez juste la clé pour récupérer la phrase anglaise: `loc('diagnostic')`.
|
||||
|
Loading…
x
Reference in New Issue
Block a user