decidim-comments 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +42 -0
- data/Rakefile +2 -0
- data/app/assets/config/decidim_comments_manifest.js +1 -0
- data/app/assets/javascripts/decidim/comments/bundle.js +504 -0
- data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -0
- data/app/assets/javascripts/decidim/comments/comments.js.erb +8 -0
- data/app/commands/decidim/comments/create_comment.rb +40 -0
- data/app/forms/decidim/comments/comment_form.rb +16 -0
- data/app/frontend/application/apollo_client.js +16 -0
- data/app/frontend/application/application.component.jsx +37 -0
- data/app/frontend/application/application.component.test.jsx +33 -0
- data/app/frontend/application/icon.component.jsx +21 -0
- data/app/frontend/application/icon.component.test.jsx +43 -0
- data/app/frontend/comments/add_comment_form.component.jsx +250 -0
- data/app/frontend/comments/add_comment_form.component.test.jsx +173 -0
- data/app/frontend/comments/add_comment_form.mutation.graphql +8 -0
- data/app/frontend/comments/comment.component.jsx +202 -0
- data/app/frontend/comments/comment.component.test.jsx +125 -0
- data/app/frontend/comments/comment.fragment.graphql +12 -0
- data/app/frontend/comments/comment_data.fragment.graphql +11 -0
- data/app/frontend/comments/comment_order_selector.component.jsx +28 -0
- data/app/frontend/comments/comment_order_selector.component.test.jsx +9 -0
- data/app/frontend/comments/comment_thread.component.jsx +64 -0
- data/app/frontend/comments/comment_thread.component.test.jsx +71 -0
- data/app/frontend/comments/comment_thread.fragment.graphql +9 -0
- data/app/frontend/comments/comments.component.jsx +139 -0
- data/app/frontend/comments/comments.component.test.jsx +106 -0
- data/app/frontend/comments/comments.query.graphql +10 -0
- data/app/frontend/comments/featured_comment.component.jsx +23 -0
- data/app/frontend/comments/featured_comment.component.test.jsx +15 -0
- data/app/frontend/entry.js +24 -0
- data/app/frontend/entry.test.js +29 -0
- data/app/frontend/support/asset_url.js +11 -0
- data/app/frontend/support/generate_comments_data.js +29 -0
- data/app/frontend/support/generate_current_user_data.js +13 -0
- data/app/frontend/support/load_translations.js +23 -0
- data/app/frontend/support/require_all.js +10 -0
- data/app/frontend/support/resolve_graphql_query.js +37 -0
- data/app/frontend/support/stub_component.js +29 -0
- data/app/helpers/decidim/comments/comments_helper.rb +51 -0
- data/app/models/decidim/comments/comment.rb +55 -0
- data/app/types/decidim/comments/add_comment_type.rb +12 -0
- data/app/types/decidim/comments/author_type.rb +15 -0
- data/app/types/decidim/comments/comment_type.rb +28 -0
- data/config/i18n-tasks.yml +124 -0
- data/config/locales/ca.yml +35 -0
- data/config/locales/en.yml +36 -0
- data/config/locales/es.yml +35 -0
- data/db/migrate/20161130143508_create_comments.rb +11 -0
- data/db/migrate/20161214082645_add_depth_to_comments.rb +5 -0
- data/db/migrate/20161216102820_add_alignment_to_comments.rb +5 -0
- data/db/seeds.rb +11 -0
- data/lib/decidim/comments.rb +10 -0
- data/lib/decidim/comments/engine.rb +34 -0
- data/lib/decidim/comments/mutation_extensions.rb +36 -0
- data/lib/decidim/comments/query_extensions.rb +33 -0
- metadata +228 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
import { Component } from 'react';
|
2
|
+
import { I18n } from 'react-i18nify';
|
3
|
+
|
4
|
+
import Comment from './comment.component';
|
5
|
+
|
6
|
+
/**
|
7
|
+
* A wrapper component for a highlighted component.
|
8
|
+
* @class
|
9
|
+
* @augments Component
|
10
|
+
* @todo It's not used right now
|
11
|
+
*/
|
12
|
+
export default class FeaturedComment extends Component {
|
13
|
+
render() {
|
14
|
+
return (
|
15
|
+
<section className="comments">
|
16
|
+
<h4 className="section-heading">{ I18n.t("components.featured_comment.title") }</h4>
|
17
|
+
<div className="comment-thread comment--pinned">
|
18
|
+
<Comment />
|
19
|
+
</div>
|
20
|
+
</section>
|
21
|
+
);
|
22
|
+
}
|
23
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { shallow } from 'enzyme';
|
2
|
+
|
3
|
+
import FeaturedComment from './featured_comment.component';
|
4
|
+
import Comment from './comment.component';
|
5
|
+
|
6
|
+
import stubComponent from '../support/stub_component';
|
7
|
+
|
8
|
+
describe('<FeaturedComment />', () => {
|
9
|
+
stubComponent(Comment);
|
10
|
+
|
11
|
+
it("should render a section of class comments", () => {
|
12
|
+
const wrapper = shallow(<FeaturedComment />);
|
13
|
+
expect(wrapper.find('section.comments')).to.be.present();
|
14
|
+
});
|
15
|
+
});
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import ReactDOM from 'react-dom';
|
2
|
+
|
3
|
+
import loadTranslations from './support/load_translations';
|
4
|
+
import Comments from './comments/comments.component';
|
5
|
+
|
6
|
+
// Expose global components
|
7
|
+
window.DecidimComments.renderCommentsComponent = (nodeId, props) => {
|
8
|
+
var node = $(`#${nodeId}`)[0];
|
9
|
+
|
10
|
+
ReactDOM.render(
|
11
|
+
React.createElement(Comments,props),
|
12
|
+
node
|
13
|
+
);
|
14
|
+
|
15
|
+
function unmountComponent() {
|
16
|
+
ReactDOM.unmountComponentAtNode(node);
|
17
|
+
$(document).off('turbolinks:before-render', unmountComponent);
|
18
|
+
}
|
19
|
+
|
20
|
+
$(document).on('turbolinks:before-render', unmountComponent);
|
21
|
+
};
|
22
|
+
|
23
|
+
// Load component locales from yaml files
|
24
|
+
loadTranslations();
|
@@ -0,0 +1,29 @@
|
|
1
|
+
// ---------------------------------------
|
2
|
+
// Test Environment Setup
|
3
|
+
// ---------------------------------------
|
4
|
+
import sinon from 'sinon/pkg/sinon';
|
5
|
+
import chai from 'chai';
|
6
|
+
import sinonChai from 'sinon-chai';
|
7
|
+
import chaiAsPromised from 'chai-as-promised';
|
8
|
+
import chaiEnzyme from 'chai-enzyme';
|
9
|
+
import loadTranslations from './support/load_translations';
|
10
|
+
import requireAll from './support/require_all';
|
11
|
+
|
12
|
+
//
|
13
|
+
chai.use(sinonChai)
|
14
|
+
chai.use(chaiAsPromised)
|
15
|
+
chai.use(chaiEnzyme())
|
16
|
+
//
|
17
|
+
window.chai = chai
|
18
|
+
window.sinon = sinon
|
19
|
+
window.expect = chai.expect
|
20
|
+
window.should = chai.should()
|
21
|
+
|
22
|
+
// ---------------------------------------
|
23
|
+
// Require Tests
|
24
|
+
// ---------------------------------------
|
25
|
+
requireAll(require.context('./application/', true, /\.test\.jsx?$/));
|
26
|
+
requireAll(require.context('./comments/', true, /\.test\.jsx?$/));
|
27
|
+
|
28
|
+
// Load component locales from yaml files
|
29
|
+
loadTranslations();
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import { random, name, date, image } from 'faker/locale/en';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Generate random comment data to emulate a database real content
|
5
|
+
* @param {number} num - The number of comments to generate random data
|
6
|
+
* @returns {Object[]} - An array of objects representing comments data
|
7
|
+
*/
|
8
|
+
const generateCommentsData = (num = 1) => {
|
9
|
+
let commentsData = [];
|
10
|
+
|
11
|
+
for (let idx = 0; idx < num; idx += 1) {
|
12
|
+
commentsData.push({
|
13
|
+
id: random.uuid(),
|
14
|
+
body: random.words(),
|
15
|
+
createdAt: date.past().toString(),
|
16
|
+
author: {
|
17
|
+
name: name.findName(),
|
18
|
+
avatarUrl: image.imageUrl()
|
19
|
+
},
|
20
|
+
replies: [],
|
21
|
+
canHaveReplies: true,
|
22
|
+
alignment: 0
|
23
|
+
})
|
24
|
+
}
|
25
|
+
|
26
|
+
return commentsData;
|
27
|
+
};
|
28
|
+
|
29
|
+
export default generateCommentsData;
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { name } from 'faker/locale/en';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Generate random current user data to emulate a database real content
|
5
|
+
* @returns {Object} - An object representing current user data
|
6
|
+
*/
|
7
|
+
const generateCurrentUserData = () => {
|
8
|
+
return {
|
9
|
+
name: name.findName()
|
10
|
+
};
|
11
|
+
};
|
12
|
+
|
13
|
+
export default generateCurrentUserData;
|
@@ -0,0 +1,23 @@
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
2
|
+
import { I18n } from 'react-i18nify';
|
3
|
+
import requireAll from './require_all';
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Load components translations from yaml files and import them into
|
7
|
+
* react-i18ify system so they can be used via `I18n.t` method.
|
8
|
+
* @returns {Void} - Nothing
|
9
|
+
*/
|
10
|
+
const loadTranslations = () => {
|
11
|
+
const translationsContext = require.context('../../../config/locales/', true, /\.yml$/);
|
12
|
+
const translationFiles = requireAll(translationsContext);
|
13
|
+
|
14
|
+
const translations = translationsContext.keys().reduce((acc, key, index) => {
|
15
|
+
const locale = key.match(/\.\/(.*)\.yml/)[1];
|
16
|
+
acc[locale] = translationFiles[index][locale].decidim;
|
17
|
+
return acc;
|
18
|
+
}, {});
|
19
|
+
|
20
|
+
I18n.setTranslations(translations);
|
21
|
+
};
|
22
|
+
|
23
|
+
export default loadTranslations;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
/**
|
2
|
+
* Given a webpack require context it require all the files
|
3
|
+
* @param {Object} requireContext - A webpack require context
|
4
|
+
* @returns {Object[]} - An array of webpack modules
|
5
|
+
*/
|
6
|
+
const requireAll = (requireContext) => {
|
7
|
+
return requireContext.keys().map(requireContext);
|
8
|
+
};
|
9
|
+
|
10
|
+
export default requireAll;
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import graphql, { filter } from 'graphql-anywhere';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* A simple resolver which returns object properties to easily
|
5
|
+
* traverse a graphql response
|
6
|
+
* @param {String} fieldName - An object property
|
7
|
+
* @param {Object} root - An object
|
8
|
+
* @returns {any} - An object's property value
|
9
|
+
*/
|
10
|
+
const resolver = (fieldName, root) => root[fieldName];
|
11
|
+
|
12
|
+
/**
|
13
|
+
* A helper function to mock a graphql api request and return its
|
14
|
+
* result. The result can be filtered by the same query so it just
|
15
|
+
* returns a data subset.
|
16
|
+
* @param {String} document - A graphql query document
|
17
|
+
* @param {options} options - An object with optional options
|
18
|
+
* @returns {Object} - The result of the query filtered or not
|
19
|
+
*/
|
20
|
+
const resolveGraphQLQuery = (document, options = {}) => {
|
21
|
+
const { filterResult, rootValue, context, variables } = options;
|
22
|
+
|
23
|
+
let result = graphql(
|
24
|
+
resolver,
|
25
|
+
document,
|
26
|
+
rootValue,
|
27
|
+
context,
|
28
|
+
variables
|
29
|
+
);
|
30
|
+
|
31
|
+
if (filterResult) {
|
32
|
+
return filter(document, result);
|
33
|
+
}
|
34
|
+
return result;
|
35
|
+
}
|
36
|
+
|
37
|
+
export default resolveGraphQLQuery;
|
@@ -0,0 +1,29 @@
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
2
|
+
|
3
|
+
/**
|
4
|
+
* A helper function to stub the `propTypes` and `fragments` of a component.
|
5
|
+
* Useful for testing isolated components so the children propTypes are not
|
6
|
+
* evaluated.
|
7
|
+
* @param {ReactComponent} componentClass - A component constructor function or class
|
8
|
+
* @param {Object} options - An object which properties are used to stub component properties.
|
9
|
+
* @returns {ReactComponent} - A component with some properties stubbed
|
10
|
+
*/
|
11
|
+
const stubComponent = function(componentClass, options = {}) {
|
12
|
+
let originalPropTypes = {};
|
13
|
+
let originalFragments = {};
|
14
|
+
|
15
|
+
beforeEach(function() {
|
16
|
+
originalPropTypes = componentClass.propTypes;
|
17
|
+
originalFragments = componentClass.fragments;
|
18
|
+
|
19
|
+
componentClass.propTypes = options.propTypes || {};
|
20
|
+
componentClass.fragments = options.fragments || {};
|
21
|
+
});
|
22
|
+
|
23
|
+
afterEach(function() {
|
24
|
+
componentClass.propTypes = originalPropTypes;
|
25
|
+
componentClass.fragments = originalFragments;
|
26
|
+
});
|
27
|
+
};
|
28
|
+
|
29
|
+
export default stubComponent;
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Comments
|
4
|
+
# A helper to expose the comments component for a commentable
|
5
|
+
module CommentsHelper
|
6
|
+
# Creates a Comments component which is rendered using `react_ujs`
|
7
|
+
# from react-rails gem
|
8
|
+
#
|
9
|
+
# resource - A commentable resource
|
10
|
+
# options - A hash of options (default: {})
|
11
|
+
# :arguable - A boolean value to indicate if tihs option is available
|
12
|
+
#
|
13
|
+
# Returns a div which contain a RectComponent to be rendered by `react_ujs`
|
14
|
+
def comments_for(resource, options = {})
|
15
|
+
commentable_type = resource.class.name
|
16
|
+
commentable_id = resource.id.to_s
|
17
|
+
node_id = "comments-for-#{commentable_type.demodulize}-#{commentable_id}"
|
18
|
+
|
19
|
+
react_comments_component(node_id, commentableType: commentable_type,
|
20
|
+
commentableId: commentable_id,
|
21
|
+
options: options.slice(:arguable),
|
22
|
+
locale: I18n.locale)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Private: Render Comments component using inline javascript
|
26
|
+
#
|
27
|
+
# node_id - The id of the DOMElement to render the React component
|
28
|
+
# props - A hash corresponding to Comments component props
|
29
|
+
def react_comments_component(node_id, props)
|
30
|
+
content_tag("div", "", id: node_id) +
|
31
|
+
javascript_tag(%{
|
32
|
+
jQuery.ajax({
|
33
|
+
url: '#{asset_path("decidim/comments/comments.js")}',
|
34
|
+
dataType: 'script',
|
35
|
+
cache: true
|
36
|
+
}).then(function () {
|
37
|
+
window.DecidimComments.renderCommentsComponent(
|
38
|
+
'#{node_id}',
|
39
|
+
{
|
40
|
+
commentableType: "#{props[:commentableType]}",
|
41
|
+
commentableId: "#{props[:commentableId]}",
|
42
|
+
options: JSON.parse("#{j(props[:options].to_json)}"),
|
43
|
+
locale: "#{props[:locale]}"
|
44
|
+
}
|
45
|
+
);
|
46
|
+
});
|
47
|
+
})
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Comments
|
4
|
+
# Some resources will be configured as commentable objects so users can
|
5
|
+
# comment on them. The will be able to create conversations between users
|
6
|
+
# to discuss or share their thoughts about the resource.
|
7
|
+
class Comment < ApplicationRecord
|
8
|
+
# Limit the max depth of a comment tree. If C is a comment and R is a reply:
|
9
|
+
# C (depth 0)
|
10
|
+
# |--R (depth 1)
|
11
|
+
# |--R (depth 1)
|
12
|
+
# |--R (depth 2)
|
13
|
+
# |--R (depth 3)
|
14
|
+
MAX_DEPTH = 3
|
15
|
+
|
16
|
+
belongs_to :author, class_name: Decidim::User
|
17
|
+
belongs_to :commentable, polymorphic: true
|
18
|
+
has_many :replies, as: :commentable, class_name: Comment
|
19
|
+
|
20
|
+
validates :author, :commentable, :body, presence: true
|
21
|
+
validate :commentable_can_have_replies
|
22
|
+
validates :depth, numericality: { greater_than_or_equal_to: 0 }
|
23
|
+
validates :alignment, inclusion: { in: [0, 1, -1] }
|
24
|
+
validate :same_organization
|
25
|
+
|
26
|
+
before_save :compute_depth
|
27
|
+
|
28
|
+
delegate :organization, to: :commentable
|
29
|
+
|
30
|
+
# Public: Define if a comment can have replies or not
|
31
|
+
#
|
32
|
+
# Returns a bool value to indicate if comment can have replies
|
33
|
+
def can_have_replies?
|
34
|
+
depth < MAX_DEPTH
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# Private: Check if commentable can have replies and if not adds
|
40
|
+
# a validation error to the model
|
41
|
+
def commentable_can_have_replies
|
42
|
+
errors.add(:commentable, :cannot_have_replies) if commentable.respond_to?(:can_have_replies?) && !commentable.can_have_replies?
|
43
|
+
end
|
44
|
+
|
45
|
+
# Private: Compute comment depth inside the current comment tree
|
46
|
+
def compute_depth
|
47
|
+
self.depth = commentable.depth + 1 if commentable.respond_to?(:depth)
|
48
|
+
end
|
49
|
+
|
50
|
+
def same_organization
|
51
|
+
errors.add(:commentable, :invalid) unless author.organization == organization
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Comments
|
4
|
+
# This type represents a mutation to create new comments.
|
5
|
+
AddCommentType = GraphQL::ObjectType.define do
|
6
|
+
name "Add comment"
|
7
|
+
description "Add a new comment"
|
8
|
+
|
9
|
+
field :comment, CommentType, "The new created comment"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Comments
|
4
|
+
# This type represents an author who owns a resource
|
5
|
+
AuthorType = GraphQL::ObjectType.define do
|
6
|
+
name "Author"
|
7
|
+
description "An author"
|
8
|
+
|
9
|
+
field :name, !types.String, "The user's name"
|
10
|
+
field :avatarUrl, !types.String, "The user's avatar url" do
|
11
|
+
resolve ->(obj, _args, _ctx) { obj.avatar.url }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Decidim
|
3
|
+
module Comments
|
4
|
+
# This type represents a comment on a commentable object.
|
5
|
+
CommentType = GraphQL::ObjectType.define do
|
6
|
+
name "Comment"
|
7
|
+
description "A comment"
|
8
|
+
|
9
|
+
field :id, !types.ID, "The Comment's unique ID"
|
10
|
+
|
11
|
+
field :body, !types.String, "The comment message"
|
12
|
+
|
13
|
+
field :createdAt, !types.String, "The creation date of the comment" do
|
14
|
+
property :created_at
|
15
|
+
end
|
16
|
+
|
17
|
+
field :author, !AuthorType, "The comment's author"
|
18
|
+
|
19
|
+
field :replies, !types[CommentType], "The comment's replies"
|
20
|
+
|
21
|
+
field :canHaveReplies, !types.Boolean, "Define if a comment can or not have replies" do
|
22
|
+
property :can_have_replies?
|
23
|
+
end
|
24
|
+
|
25
|
+
field :alignment, types.Int, "The comment's alignment. Can be 0 (neutral), 1 (in favor) or -1 (against)'"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# i18n-tasks finds and manages missing and unused translations: https://github.com/glebm/i18n-tasks
|
2
|
+
|
3
|
+
# The "main" locale.
|
4
|
+
base_locale: en
|
5
|
+
## All available locales are inferred from the data by default. Alternatively, specify them explicitly:
|
6
|
+
locales: [en]
|
7
|
+
## Reporting locale, default: en. Available: en, ru.
|
8
|
+
# internal_locale: en
|
9
|
+
|
10
|
+
# Read and write translations.
|
11
|
+
data:
|
12
|
+
## Translations are read from the file system. Supported format: YAML, JSON.
|
13
|
+
## Provide a custom adapter:
|
14
|
+
# adapter: I18n::Tasks::Data::FileSystem
|
15
|
+
|
16
|
+
# Locale files or `File.find` patterns where translations are read from:
|
17
|
+
read:
|
18
|
+
## Default:
|
19
|
+
# - config/locales/%{locale}.yml
|
20
|
+
## More files:
|
21
|
+
# - config/locales/**/*.%{locale}.yml
|
22
|
+
## Another gem (replace %#= with %=):
|
23
|
+
# - "<%#= %x[bundle show vagrant].chomp %>/templates/locales/%{locale}.yml"
|
24
|
+
|
25
|
+
# Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom:
|
26
|
+
# `i18n-tasks normalize -p` will force move the keys according to these rules
|
27
|
+
write:
|
28
|
+
## For example, write devise and simple form keys to their respective files:
|
29
|
+
# - ['{devise, simple_form}.*', 'config/locales/\1.%{locale}.yml']
|
30
|
+
## Catch-all default:
|
31
|
+
# - config/locales/%{locale}.yml
|
32
|
+
|
33
|
+
## Specify the router (see Readme for details). Valid values: conservative_router, pattern_router, or a custom class.
|
34
|
+
# router: convervative_router
|
35
|
+
|
36
|
+
yaml:
|
37
|
+
write:
|
38
|
+
# do not wrap lines at 80 characters
|
39
|
+
line_width: -1
|
40
|
+
|
41
|
+
## Pretty-print JSON:
|
42
|
+
# json:
|
43
|
+
# write:
|
44
|
+
# indent: ' '
|
45
|
+
# space: ' '
|
46
|
+
# object_nl: "\n"
|
47
|
+
# array_nl: "\n"
|
48
|
+
|
49
|
+
# Find translate calls
|
50
|
+
search:
|
51
|
+
## Paths or `File.find` patterns to search in:
|
52
|
+
# paths:
|
53
|
+
# - app/
|
54
|
+
|
55
|
+
## Root directories for relative keys resolution.
|
56
|
+
# relative_roots:
|
57
|
+
# - app/controllers
|
58
|
+
# - app/helpers
|
59
|
+
# - app/mailers
|
60
|
+
# - app/presenters
|
61
|
+
# - app/views
|
62
|
+
|
63
|
+
## Files or `File.fnmatch` patterns to exclude from search. Some files are always excluded regardless of this setting:
|
64
|
+
## %w(*.jpg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less *.yml *.json)
|
65
|
+
exclude:
|
66
|
+
- app/assets/images
|
67
|
+
- app/assets/fonts
|
68
|
+
- app/assets/javascripts/decidim/comments/bundle.js
|
69
|
+
- app/assets/javascripts/decidim/comments/bundle.js.map
|
70
|
+
|
71
|
+
## Alternatively, the only files or `File.fnmatch patterns` to search in `paths`:
|
72
|
+
## If specified, this settings takes priority over `exclude`, but `exclude` still applies.
|
73
|
+
# only: ["*.rb", "*.html.slim"]
|
74
|
+
|
75
|
+
## If `strict` is `false`, guess usages such as t("categories.#{category}.title"). The default is `true`.
|
76
|
+
# strict: true
|
77
|
+
|
78
|
+
## Multiple scanners can be used. Their results are merged.
|
79
|
+
## The options specified above are passed down to each scanner. Per-scanner options can be specified as well.
|
80
|
+
## See this example of a custom scanner: https://github.com/glebm/i18n-tasks/wiki/A-custom-scanner-example
|
81
|
+
|
82
|
+
## Google Translate
|
83
|
+
# translation:
|
84
|
+
# # Get an API key and set billing info at https://code.google.com/apis/console to use Google Translate
|
85
|
+
# api_key: "AbC-dEf5"
|
86
|
+
|
87
|
+
## Do not consider these keys missing:
|
88
|
+
ignore_missing:
|
89
|
+
- 'components.*'
|
90
|
+
# - 'errors.messages.{accepted,blank,invalid,too_short,too_long}'
|
91
|
+
# - '{devise,simple_form}.*'
|
92
|
+
|
93
|
+
## Consider these keys used:
|
94
|
+
ignore_unused:
|
95
|
+
- 'activerecord.errors.messages.*'
|
96
|
+
- 'decidim.components.*'
|
97
|
+
# - '{devise,kaminari,will_paginate}.*'
|
98
|
+
# - 'simple_form.{yes,no}'
|
99
|
+
# - 'simple_form.{placeholders,hints,labels}.*'
|
100
|
+
# - 'simple_form.{error_notification,required}.:'
|
101
|
+
|
102
|
+
## Exclude these keys from the `i18n-tasks eq-base' report:
|
103
|
+
# ignore_eq_base:
|
104
|
+
# all:
|
105
|
+
# - common.ok
|
106
|
+
# fr,es:
|
107
|
+
# - common.brand
|
108
|
+
|
109
|
+
## Ignore these keys completely:
|
110
|
+
# ignore:
|
111
|
+
# - kaminari.*
|
112
|
+
|
113
|
+
## Sometimes, it isn't possible for i18n-tasks to match the key correctly,
|
114
|
+
## e.g. in case of a relative key defined in a helper method.
|
115
|
+
## In these cases you can use the built-in PatternMapper to map patterns to keys, e.g.:
|
116
|
+
#
|
117
|
+
# <%#= I18n::Tasks.add_scanner 'I18n::Tasks::Scanners::PatternMapper',
|
118
|
+
# only: %w(*.html.haml *.html.slim),
|
119
|
+
# patterns: [['= title\b', '.page_title']] %>
|
120
|
+
#
|
121
|
+
# The PatternMapper can also match key literals via a special %{key} interpolation, e.g.:
|
122
|
+
#
|
123
|
+
# <%#= I18n::Tasks.add_scanner 'I18n::Tasks::Scanners::PatternMapper',
|
124
|
+
# patterns: [['\bSpree\.t[( ]\s*%{key}', 'spree.%{key}']] %>
|