zammad-detect-translatable-string.js 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. // Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. /**
  3. * @fileoverview Detect unmarked translatable strings
  4. * @author Martin Gruner
  5. */
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. /**
  10. * @type {import('eslint').Rule.RuleModule}
  11. */
  12. module.exports = {
  13. meta: {
  14. type: 'problem',
  15. docs: {
  16. description: 'Detect unmarked translatable strings',
  17. category: 'Layout & Formatting',
  18. recommended: true,
  19. url: null,
  20. },
  21. fixable: 'code',
  22. schema: [],
  23. },
  24. create(context) {
  25. const IGNORE_STRING_PATTERNS = [
  26. /^[^A-Z]/, // Only look at strings starting with upper case letters
  27. /\$\{/, // Ignore strings with interpolation
  28. ]
  29. const IGNORE_METHODS = ['__']
  30. const IGNORE_OBJECTS = ['log', 'console', 'i18n']
  31. return {
  32. Literal(node) {
  33. if (typeof node.value !== 'string') {
  34. return
  35. }
  36. const string = node.value
  37. // Ignore strings with less than two words.
  38. if (string.split(' ').length < 2) return
  39. for (const pattern of IGNORE_STRING_PATTERNS) {
  40. if (string.match(pattern)) return
  41. }
  42. // Ignore strings used for comparison
  43. const tokenBefore = context.getTokenBefore(node)
  44. if (
  45. tokenBefore &&
  46. tokenBefore.type === 'Punctuator' &&
  47. ['==', '==='].includes(tokenBefore.value)
  48. ) {
  49. return
  50. }
  51. const { parent } = node
  52. if (parent.type === 'CallExpression') {
  53. if (IGNORE_METHODS.includes(parent.callee.name)) return
  54. if (
  55. parent.callee.type === 'MemberExpression' &&
  56. IGNORE_OBJECTS.includes(parent.callee.object.name)
  57. ) {
  58. return
  59. }
  60. }
  61. context.report({
  62. node,
  63. message:
  64. 'This string looks like it should be marked as translatable via __(...)',
  65. fix(fixer) {
  66. return fixer.replaceText(node, `__(${node.raw})`)
  67. },
  68. })
  69. },
  70. }
  71. },
  72. }