Add [Weblate] badges (#6677)

* feat: add weblate badges

* fix: use color-formatter for translated-percentage

* fix: use metric formatter

* rm: removed units badge

* test: use createservicetester

Co-authored-by: Caleb Cartwright <calebcartwright@users.noreply.github.com>

* fix: refactor weblate badges

Co-authored-by: Caleb Cartwright <calebcartwright@users.noreply.github.com>
Co-authored-by: repo-ranger[bot] <39074581+repo-ranger[bot]@users.noreply.github.com>
This commit is contained in:
Seth Falco
2021-07-02 04:58:00 +02:00
committed by GitHub
parent 6aae41e1c3
commit 384c57eb47
8 changed files with 358 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
'use strict'
const Joi = require('joi')
const { BaseJsonService } = require('..')
const { optionalUrl } = require('../validators')
const schema = Joi.object({
license: Joi.string().required(),
}).required()
const queryParamSchema = Joi.object({
server: optionalUrl.required(),
}).required()
/**
* This badge displays the license of a component on a Weblate instance.
*/
module.exports = class WeblateComponentLicense extends BaseJsonService {
static category = 'license'
static route = {
base: 'weblate/license',
pattern: ':project/:component',
queryParamSchema,
}
static examples = [
{
title: 'Weblate component license',
namedParams: { project: 'godot-engine', component: 'godot' },
queryParams: { server: 'https://hosted.weblate.org' },
staticPreview: this.render({ license: 'MIT' }),
keywords: ['i18n', 'translation', 'internationalization'],
},
]
static defaultBadgeData = { label: 'license', color: 'informational' }
static render({ license }) {
return { message: `${license}` }
}
async fetch({ project, component, server }) {
return this._requestJson({
schema,
url: `${server}/api/components/${project}/${component}/`,
errorMessages: {
403: 'access denied by remote server',
404: 'component not found',
429: 'rate limited by remote server',
},
})
}
async handle({ project, component }, { server }) {
const { license } = await this.fetch({ project, component, server })
return this.constructor.render({ license })
}
}

View File

@@ -0,0 +1,11 @@
'use strict'
const t = (module.exports = require('../tester').createServiceTester())
t.create('License')
.get('/godot-engine/godot.json?server=https://hosted.weblate.org')
.expectBadge({ label: 'license', message: 'MIT' })
t.create("Component Doesn't Exist")
.get('/fake-project/fake-component.json?server=https://hosted.weblate.org')
.expectBadge({ label: 'license', message: 'component not found' })

View File

@@ -0,0 +1,74 @@
'use strict'
const Joi = require('joi')
const camelcase = require('camelcase')
const { BaseJsonService } = require('..')
const { nonNegativeInteger, optionalUrl } = require('../validators')
const { metric } = require('../text-formatters')
const schema = Joi.object({
count: nonNegativeInteger,
}).required()
const queryParamSchema = Joi.object({
server: optionalUrl.required(),
}).required()
class WeblateEntityCountBase extends BaseJsonService {
static category = 'other'
static buildRoute(entityName) {
return {
base: 'weblate',
pattern: entityName,
queryParamSchema,
}
}
static defaultBadgeData = { color: 'informational' }
async fetch({ entityName, server }) {
return this._requestJson({
schema,
url: `${server}/api/${entityName}/`,
errorMessages: {
403: 'access denied by remote server',
429: 'rate limited by remote server',
},
})
}
}
function WeblateEntityCountFactory({ entityName, exampleValue }) {
return class WeblateEntityCountService extends WeblateEntityCountBase {
static name = camelcase(`Weblate ${entityName}`, { pascalCase: true })
static route = this.buildRoute(entityName)
static examples = [
{
title: `Weblate ${entityName}`,
namedParams: {},
queryParams: { server: 'https://hosted.weblate.org' },
staticPreview: this.render({ count: exampleValue }),
keywords: ['i18n', 'internationalization'],
},
]
static render({ count }) {
return { label: entityName, message: metric(count) }
}
async handle(routeParams, { server }) {
const { count } = await this.fetch({ entityName, server })
return this.constructor.render({ count })
}
}
}
const entityCounts = [
{ entityName: 'components', exampleValue: 2799 },
{ entityName: 'projects', exampleValue: 533 },
{ entityName: 'users', exampleValue: 33058 },
].map(WeblateEntityCountFactory)
module.exports = [...entityCounts]

View File

@@ -0,0 +1,22 @@
'use strict'
const { ServiceTester } = require('../tester')
const { isMetric } = require('../test-validators')
const t = (module.exports = new ServiceTester({
id: 'WeblateEntity',
title: 'Weblate Entity',
pathPrefix: '/weblate',
}))
t.create('Components')
.get('/components.json?server=https://hosted.weblate.org')
.expectBadge({ label: 'components', message: isMetric })
t.create('Projects')
.get('/projects.json?server=https://hosted.weblate.org')
.expectBadge({ label: 'projects', message: isMetric })
t.create('Users')
.get('/users.json?server=https://hosted.weblate.org')
.expectBadge({ label: 'users', message: isMetric })

View File

@@ -0,0 +1,68 @@
'use strict'
const Joi = require('joi')
const { BaseJsonService } = require('..')
const { optionalUrl } = require('../validators')
const { colorScale } = require('../color-formatters')
const schema = Joi.object({
translated_percent: Joi.number().required(),
}).required()
const queryParamSchema = Joi.object({
server: optionalUrl.required(),
}).required()
/**
* This badge displays the percentage of strings translated on a project on a
* Weblate instance.
*/
module.exports = class WeblateProjectTranslatedPercentage extends (
BaseJsonService
) {
static category = 'other'
static route = { base: 'weblate', pattern: ':project', queryParamSchema }
static examples = [
{
title: 'Weblate project translated',
namedParams: { project: 'godot-engine' },
queryParams: { server: 'https://hosted.weblate.org' },
staticPreview: this.render({ translatedPercent: 20.5 }),
keywords: ['i18n', 'translation', 'internationalization'],
},
]
static defaultBadgeData = { label: 'translated' }
/**
* Takes a percentage and maps it to a message and color.
*
* The colors are determined based on how Weblate does it internally.
* {@link https://github.com/WeblateOrg/weblate/blob/main/weblate/trans/widgets.py Weblate on GitHub}
*
* @param {*} translatedPercent The percentage of translations translated.
* @returns {object} Format for the badge.
*/
static render({ translatedPercent }) {
const color = colorScale([75, 90])(translatedPercent)
return { message: `${translatedPercent.toFixed(0)}%`, color }
}
async fetch({ project, server }) {
return this._requestJson({
schema,
url: `${server}/api/projects/${project}/statistics/`,
errorMessages: {
403: 'access denied by remote server',
404: 'project not found',
429: 'rate limited by remote server',
},
})
}
async handle({ project }, { server }) {
const { translated_percent } = await this.fetch({ project, server })
return this.constructor.render({ translatedPercent: translated_percent })
}
}

View File

@@ -0,0 +1,13 @@
'use strict'
const t = (module.exports = require('../tester').createServiceTester())
const { isPercentage } = require('../test-validators')
t.create('License')
.get('/godot-engine.json?server=https://hosted.weblate.org')
.expectBadge({ label: 'translated', message: isPercentage })
t.create('Not Valid')
.get('/fake-project.json?server=https://hosted.weblate.org')
.expectBadge({ label: 'translated', message: 'project not found' })

View File

@@ -0,0 +1,90 @@
'use strict'
const Joi = require('joi')
const camelcase = require('camelcase')
const { BaseJsonService } = require('..')
const { nonNegativeInteger, optionalUrl } = require('../validators')
const { metric } = require('../text-formatters')
const schema = Joi.object({
translated: nonNegativeInteger,
suggested: nonNegativeInteger,
uploaded: nonNegativeInteger,
commented: nonNegativeInteger,
languages: nonNegativeInteger,
}).required()
const queryParamSchema = Joi.object({
server: optionalUrl.required(),
}).required()
class WeblateUserStatisticBase extends BaseJsonService {
static category = 'other'
static buildRoute(statistic) {
return {
base: 'weblate/user',
pattern: `:user/${statistic}`,
queryParamSchema,
}
}
static defaultBadgeData = { color: 'informational' }
async fetch({ user, server }) {
return this._requestJson({
schema,
url: `${server}/api/users/${user}/statistics/`,
errorMessages: {
403: 'access denied by remote server',
404: 'user not found',
429: 'rate limited by remote server',
},
})
}
}
function WeblateUserStatisticFactory({
statisticName,
property,
exampleValue,
}) {
return class WeblateUserStatistic extends WeblateUserStatisticBase {
static name = camelcase(`Weblate user ${statisticName}`, {
pascalCase: true,
})
static route = this.buildRoute(statisticName)
static examples = [
{
title: `Weblate user ${statisticName}`,
namedParams: { user: 'nijel' },
queryParams: { server: 'https://hosted.weblate.org' },
staticPreview: this.render({ count: exampleValue }),
keywords: ['i18n', 'internationalization'],
},
]
static render({ count }) {
return { label: statisticName, message: metric(count) }
}
async handle({ user }, { server }) {
const data = await this.fetch({ user, server })
return this.constructor.render({ count: data[property] })
}
}
}
const userStatistics = [
{
statisticName: 'translations',
property: 'translated',
exampleValue: 30585,
},
{ statisticName: 'suggestions', property: 'suggested', exampleValue: 7 },
{ statisticName: 'languages', property: 'languages', exampleValue: 1 },
].map(WeblateUserStatisticFactory)
module.exports = [...userStatistics]

View File

@@ -0,0 +1,22 @@
'use strict'
const { ServiceTester } = require('../tester')
const { isMetric } = require('../test-validators')
const t = (module.exports = new ServiceTester({
id: 'WeblateUserStatistic',
title: 'Weblate User Statistic',
pathPrefix: '/weblate',
}))
t.create('Translations')
.get('/user/nijel/translations.json?server=https://hosted.weblate.org')
.expectBadge({ label: 'translations', message: isMetric })
t.create('Suggestions')
.get('/user/nijel/suggestions.json?server=https://hosted.weblate.org')
.expectBadge({ label: 'suggestions', message: isMetric })
t.create('Languages')
.get('/user/nijel/languages.json?server=https://hosted.weblate.org')
.expectBadge({ label: 'languages', message: isMetric })