.eslint-plugin-zammad/lib/rules/zammad-detect-translatable-string.js
// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
/**
* @fileoverview Detect unmarked translatable strings
* @author Martin Gruner
*/
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/**
* @type {import('eslint').Rule.RuleModule}
*/
module.exports = {
meta: {
type: 'problem',
docs: {
description: 'Detect unmarked translatable strings',
category: 'Layout & Formatting',
recommended: true,
url: null,
},
fixable: 'code',
schema: [],
},
create(context) {
const IGNORE_STRING_PATTERNS = [
/^[^A-Z]/, // Only look at strings starting with upper case letters
/\$\{/, // Ignore strings with interpolation
]
const IGNORE_METHODS = ['__']
const IGNORE_OBJECTS = ['log', 'console', 'i18n']
return {
Literal(node) {
if (typeof node.value !== 'string') {
return
}
const string = node.value
// Ignore strings with less than two words.
if (string.split(' ').length < 2) return
for (const pattern of IGNORE_STRING_PATTERNS) {
if (string.match(pattern)) return
}
// Ignore strings used for comparison
const tokenBefore = context.getTokenBefore(node)
if (
tokenBefore &&
tokenBefore.type === 'Punctuator' &&
['==', '==='].includes(tokenBefore.value)
) {
return
}
const { parent } = node
if (parent.type === 'CallExpression') {
if (IGNORE_METHODS.includes(parent.callee.name)) return
if (
parent.callee.type === 'MemberExpression' &&
IGNORE_OBJECTS.includes(parent.callee.object.name)
) {
return
}
}
context.report({
node,
message:
'This string looks like it should be marked as translatable via __(...)',
fix(fixer) {
return fixer.replaceText(node, `__(${node.raw})`)
},
})
},
}
},
}