diff --git a/.circleci/config.yml b/.circleci/config.yml index 18a7a0af928..635f60c9044 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ defaults: &defaults working_directory: ~/build docker: # specify the version you desire here - - image: circleci/ruby:2.7.1-node-browsers + - image: circleci/ruby:2.7.2-node-browsers # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000000..b1bef7b2805 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,51 @@ +# pre-build stage +ARG VARIANT=2.7 +FROM mcr.microsoft.com/vscode/devcontainers/ruby:${VARIANT} + +# Update args in docker-compose.yaml to set the UID/GID of the "vscode" user. +ARG USER_UID=1000 +ARG USER_GID=$USER_UID +RUN if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then \ + groupmod --gid $USER_GID vscode \ + && usermod --uid $USER_UID --gid $USER_GID vscode \ + && chmod -R $USER_UID:$USER_GID /home/vscode; \ + fi + +# [Option] Install Node.js +ARG INSTALL_NODE="true" +ARG NODE_VERSION="lts/*" +RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + + +# tmux is for overmind +# TODO : install foreman in future +# packages: postgresql-server-dev-all +# may be postgres in same machine + +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends \ + libssl-dev \ + tar \ + tzdata \ + postgresql-client \ + yarn \ + git \ + imagemagick \ + tmux \ + zsh + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 + + +# Do the set up required for chatwoot app +WORKDIR /workspace +COPY . /workspace + +# TODO: figure out installing rvm +# RUN rvm install +COPY Gemfile Gemfile.lock ./ +RUN gem install bundler +RUN bundle install +COPY package.json yarn.lock ./ +RUN yarn install diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000000..5f878e5e067 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,33 @@ +{ + "name": "Chatwoot Development Codespace", + "service": "app", + "dockerComposeFile": "docker-compose.yml", + + "settings": { + "terminal.integrated.shell.linux": "/bin/zsh" + }, + + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "rebornix.Ruby", + "misogi.ruby-rubocop", + "wingrunr21.vscode-ruby" + ], + + + // TODO: figure whether we can get all this ports work properly + + // 3000 rails + // 3035 webpacker + // 5432 postgres + // 6379 redis + // 1025,8025 mailhog + "forwardPorts": [5432, 6379, 1025, 8025], + //your application may need to listen on all interfaces (0.0.0.0) not just localhost for it to be available externally. Defaults to [] + "appPort": [3000, 3035], + + // Use 'postCreateCommand' to run commands after the container is created. + // #TODO: can we move logic of copy env file into dockerfile ? + "postCreateCommand": "cp .env.example .env", +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 00000000000..6a2ffbea625 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,56 @@ +# https://github.com/microsoft/vscode-dev-containers/blob/master/containers/python-3-postgres/.devcontainer/docker-compose.yml +# https://github.com/microsoft/vscode-dev-containers/blob/master/containers/ruby-rails/.devcontainer/devcontainer.json +# + +version: '3' + +services: + app: + build: + context: .. + dockerfile: .devcontainer/Dockerfile + args: + # Update 'VARIANT' to pick a Ruby version: 2, 2.7, 2.6, 2.5 + VARIANT: 2.7 + # [Choice] Install Node.js + INSTALL_NODE: "true" + NODE_VERSION: "lts/*" + # On Linux, you may need to update USER_UID and USER_GID below if not your local UID is not 1000. + USER_UID: 1000 + USER_GID: 1000 + + volumes: + - ..:/workspace:cached + + # Overrides default command so things don't shut down after the process ends. + command: sleep infinity + + # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. + network_mode: service:db + + db: + image: postgres:latest + restart: unless-stopped + volumes: + - postgres-data:/var/lib/postgresql/data + environment: + POSTGRES_USER: postgres + POSTGRES_DB: postgres + POSTGRES_PASSWORD: postgres + + redis: + image: redis:latest + restart: unless-stopped + network_mode: service:db + volumes: + - redis-data:/data + + mailhog: + restart: unless-stopped + image: mailhog/mailhog + network_mode: service:db + +volumes: + postgres-data: + redis-data: + \ No newline at end of file diff --git a/.env.example b/.env.example index 4055480328d..4fe8ec3c84c 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,10 @@ SECRET_KEY_BASE=replace_with_lengthy_secure_hex # Replace with the URL you are planning to use for your app FRONTEND_URL=http://0.0.0.0:3000 +# If the variable is set, all non-authenticated pages would fallback to the default locale. +# Whenever a new account is created, the default language will be DEFAULT_LOCALE instead of en +# DEFAULT_LOCALE=en + # If you plan to use CDN for your assets, set Asset CDN Host ASSET_CDN_HOST= @@ -129,5 +133,3 @@ USE_INBOX_AVATAR_FOR_BOT=true ## Development Only Config # if you want to use letter_opener for local emails # LETTER_OPENER=true - - diff --git a/.ruby-version b/.ruby-version index 860487ca19c..37c2961c243 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.1 +2.7.2 diff --git a/Gemfile b/Gemfile index ba24662fb83..88f2fc75c47 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source 'https://rubygems.org' -ruby '2.7.1' +ruby '2.7.2' ##-- base gems for rails --## gem 'rack-cors', require: 'rack/cors' diff --git a/Gemfile.lock b/Gemfile.lock index 8b9ea88b38b..d6692e4029a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -325,7 +325,7 @@ GEM pry-rails (0.3.9) pry (>= 0.10.4) public_suffix (4.0.5) - puma (4.3.5) + puma (4.3.6) nio4r (~> 2.0) pundit (2.1.0) activesupport (>= 3.0.0) @@ -638,7 +638,7 @@ DEPENDENCIES wisper (= 2.0.0) RUBY VERSION - ruby 2.7.1p83 + ruby 2.7.2p137 BUNDLED WITH 2.1.4 diff --git a/README.md b/README.md index 0eb841bb89f..a27754a7069 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Now, a failed project is back on track and the prospects are looking great. The ### Features -Chatwoot gives an integrated view of conversations happening in different communication channels. +Chatwoot gives an integrated view of conversations happening in different communication channels. It supports the following conversation channels: @@ -44,7 +44,7 @@ It supports the following conversation channels: - **API Channel**: Build custom communication channels using our API channel. - **Email (beta)**: Forward all your email queries to Chatwoot and view it in our integrated dashboard. -Other features include: +Other features include: - **Multi-brand inboxes**: Manage multiple brands or pages using a single dashboard. - **Private notes**: Inter team communication is possible using private notes in a conversation. @@ -62,12 +62,16 @@ Other features include: Detailed documentation is available at [www.chatwoot.com/help-center](https://www.chatwoot.com/help-center). +### Translation process + +The translation process for Chatwoot web and mobile app is managed at [https://translate.chatwoot.com](https://translate.chatwoot.com) using Crowdin. Please read the [translation guide](https://www.chatwoot/docs/contributing/translating-chatwoot-to-your-language) for contributing to Chatwoot. + --- ### Branching model We use the [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) branching model. The base branch is `develop`. -If you are looking for a stable version, please use the `master` or tags labelled as `v1.x.x`. +If you are looking for a stable version, please use the `master` or tags labelled as `v1.x.x`. --- diff --git a/app.json b/app.json index 794f8de70dc..b321e9b89a7 100644 --- a/app.json +++ b/app.json @@ -34,6 +34,10 @@ "web": { "quantity": 1, "size": "FREE" + }, + "worker": { + "quantity": 1, + "size": "FREE" } }, "image": "heroku/ruby", diff --git a/app/builders/account_builder.rb b/app/builders/account_builder.rb index ae6db3b4e18..3d81a8e688c 100644 --- a/app/builders/account_builder.rb +++ b/app/builders/account_builder.rb @@ -40,7 +40,7 @@ class AccountBuilder end def create_account - @account = Account.create!(name: @account_name) + @account = Account.create!(name: @account_name, locale: I18n.locale) Current.account = @account end @@ -67,7 +67,8 @@ class AccountBuilder end def create_user - password = Time.now.to_i + password = SecureRandom.alphanumeric(12) + @user = User.new(email: @email, password: password, password_confirmation: password, diff --git a/app/controllers/api/v1/accounts/base_controller.rb b/app/controllers/api/v1/accounts/base_controller.rb index d0ae4c31d08..ef52206db0b 100644 --- a/app/controllers/api/v1/accounts/base_controller.rb +++ b/app/controllers/api/v1/accounts/base_controller.rb @@ -1,5 +1,7 @@ class Api::V1::Accounts::BaseController < Api::BaseController + include SwitchLocale before_action :current_account + around_action :switch_locale_using_account_locale private diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb index d476c744cd8..2ea843f9bec 100644 --- a/app/controllers/api/v1/accounts/contacts_controller.rb +++ b/app/controllers/api/v1/accounts/contacts_controller.rb @@ -14,7 +14,10 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController def search render json: { error: 'Specify search string with parameter q' }, status: :unprocessable_entity if params[:q].blank? && return - contacts = resolved_contacts.where('name LIKE :search OR email LIKE :search', search: "%#{params[:q]}%") + contacts = resolved_contacts.where( + 'name ILIKE :search OR email ILIKE :search OR phone_number ILIKE :search', + search: "%#{params[:q]}%" + ) @contacts_count = contacts.count @contacts = fetch_contact_last_seen_at(contacts) end @@ -32,7 +35,6 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController def create ActiveRecord::Base.transaction do @contact = Current.account.contacts.new(contact_params) - set_ip @contact.save! @contact_inbox = build_contact_inbox end @@ -40,7 +42,6 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController def update @contact.assign_attributes(contact_update_params) - set_ip @contact.save! rescue ActiveRecord::RecordInvalid => e render json: { @@ -96,11 +97,4 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController def fetch_contact @contact = Current.account.contacts.includes(contact_inboxes: [:inbox]).find(params[:id]) end - - def set_ip - return if @contact.account.feature_enabled?('ip_lookup') - - @contact[:additional_attributes][:created_at_ip] ||= request.remote_ip - @contact[:additional_attributes][:updated_at_ip] = request.remote_ip - end end diff --git a/app/controllers/api/v1/accounts/integrations/slack_controller.rb b/app/controllers/api/v1/accounts/integrations/slack_controller.rb index 1002f5752e0..5ded9f51218 100644 --- a/app/controllers/api/v1/accounts/integrations/slack_controller.rb +++ b/app/controllers/api/v1/accounts/integrations/slack_controller.rb @@ -2,13 +2,15 @@ class Api::V1::Accounts::Integrations::SlackController < Api::V1::Accounts::Base before_action :fetch_hook, only: [:update, :destroy] def create - builder = Integrations::Slack::HookBuilder.new( - account: Current.account, - code: params[:code], - inbox_id: params[:inbox_id] - ) - @hook = builder.perform - create_chatwoot_slack_channel + ActiveRecord::Base.transaction do + builder = Integrations::Slack::HookBuilder.new( + account: Current.account, + code: params[:code], + inbox_id: params[:inbox_id] + ) + @hook = builder.perform + create_chatwoot_slack_channel + end end def update diff --git a/app/controllers/api/v1/widget/base_controller.rb b/app/controllers/api/v1/widget/base_controller.rb index f8b2e2c969d..0227bd0bae6 100644 --- a/app/controllers/api/v1/widget/base_controller.rb +++ b/app/controllers/api/v1/widget/base_controller.rb @@ -1,4 +1,6 @@ class Api::V1::Widget::BaseController < ApplicationController + include SwitchLocale + before_action :set_web_widget before_action :set_contact diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 58246bb732e..8fbc8ff45c8 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,6 +1,7 @@ class ApplicationController < ActionController::Base include DeviseTokenAuth::Concerns::SetUserByToken include Pundit + include SwitchLocale protect_from_forgery with: :null_session @@ -60,28 +61,6 @@ class ApplicationController < ActionController::Base render json: exception.to_hash, status: exception.http_status end - def locale_from_params - I18n.available_locales.map(&:to_s).include?(params[:locale]) ? params[:locale] : nil - end - - def locale_from_account(account) - return unless account - - I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil - end - - def switch_locale(&action) - # priority is for locale set in query string (mostly for widget/from js sdk) - locale ||= locale_from_params - # if local is not set in param, lets try account - locale ||= locale_from_account(@current_account) - # if nothing works we rely on default locale - locale ||= I18n.default_locale - # ensure locale won't bleed into other requests - # https://guides.rubyonrails.org/i18n.html#managing-the-locale-across-requests - I18n.with_locale(locale, &action) - end - def pundit_user { user: Current.user, diff --git a/app/controllers/concerns/switch_locale.rb b/app/controllers/concerns/switch_locale.rb new file mode 100644 index 00000000000..5f308b8abb5 --- /dev/null +++ b/app/controllers/concerns/switch_locale.rb @@ -0,0 +1,42 @@ +module SwitchLocale + extend ActiveSupport::Concern + + private + + def switch_locale(&action) + # priority is for locale set in query string (mostly for widget/from js sdk) + locale ||= locale_from_params + # if locale is not set in account, let's use DEFAULT_LOCALE env variable + locale ||= locale_from_env_variable + set_locale(locale, &action) + end + + def switch_locale_using_account_locale(&action) + locale = locale_from_account(@current_account) + set_locale(locale, &action) + end + + def set_locale(locale, &action) + # if locale is empty, use default_locale + locale ||= I18n.default_locale + # Ensure locale won't bleed into other requests + # https://guides.rubyonrails.org/i18n.html#managing-the-locale-across-requests + I18n.with_locale(locale, &action) + end + + def locale_from_params + I18n.available_locales.map(&:to_s).include?(params[:locale]) ? params[:locale] : nil + end + + def locale_from_account(account) + return unless account + + I18n.available_locales.map(&:to_s).include?(account.locale) ? account.locale : nil + end + + def locale_from_env_variable + return unless ENV.fetch('DEFAULT_LOCALE', nil) + + I18n.available_locales.map(&:to_s).include?(ENV.fetch('DEFAULT_LOCALE')) ? ENV.fetch('DEFAULT_LOCALE') : nil + end +end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 27fa7fb863f..52f392adda3 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,6 +1,5 @@ class DashboardController < ActionController::Base before_action :set_global_config - layout 'vueapp' def index; end diff --git a/app/controllers/devise_overrides/sessions_controller.rb b/app/controllers/devise_overrides/sessions_controller.rb index 9ebb3b4351e..289684f17c5 100644 --- a/app/controllers/devise_overrides/sessions_controller.rb +++ b/app/controllers/devise_overrides/sessions_controller.rb @@ -2,8 +2,38 @@ class DeviseOverrides::SessionsController < ::DeviseTokenAuth::SessionsControlle # Prevent session parameter from being passed # Unpermitted parameter: session wrap_parameters format: [] + before_action :process_sso_auth_token, only: [:create] + + def create + # Authenticate user via the temporary sso auth token + if params[:sso_auth_token].present? && @resource.present? + authenticate_resource_with_sso_token + yield @resource if block_given? + render_create_success + else + super + end + end def render_create_success render partial: 'devise/auth.json', locals: { resource: @resource } end + + private + + def authenticate_resource_with_sso_token + @token = @resource.create_token + @resource.save + + sign_in(:user, @resource, store: false, bypass: false) + # invalidate the token after the user is signed in + @resource.invalidate_sso_auth_token(params[:sso_auth_token]) + end + + def process_sso_auth_token + return if params[:email].blank? + + user = User.find_by(email: params[:email]) + @resource = user if user&.valid_sso_auth_token?(params[:sso_auth_token]) + end end diff --git a/app/controllers/widgets_controller.rb b/app/controllers/widgets_controller.rb index 957203f160b..da634875a42 100644 --- a/app/controllers/widgets_controller.rb +++ b/app/controllers/widgets_controller.rb @@ -41,13 +41,21 @@ class WidgetsController < ActionController::Base def build_contact return if @contact.present? - contact_inbox = @web_widget.create_contact_inbox + contact_inbox = @web_widget.create_contact_inbox(additional_attributes) @contact = contact_inbox.contact payload = { source_id: contact_inbox.source_id, inbox_id: @web_widget.inbox.id } @token = ::Widget::TokenService.new(payload: payload).generate_token end + def additional_attributes + if @web_widget.inbox.account.feature_enabled?('ip_lookup') + { created_at_ip: request.remote_ip } + else + {} + end + end + def permitted_params params.permit(:website_token, :cw_conversation) end diff --git a/app/javascript/dashboard/App.vue b/app/javascript/dashboard/App.vue index bef1323e230..2d196af04fa 100644 --- a/app/javascript/dashboard/App.vue +++ b/app/javascript/dashboard/App.vue @@ -8,7 +8,6 @@ diff --git a/app/javascript/dashboard/routes/login/Login.vue b/app/javascript/dashboard/routes/login/Login.vue index a905864f4dc..1dfa6625da6 100644 --- a/app/javascript/dashboard/routes/login/Login.vue +++ b/app/javascript/dashboard/routes/login/Login.vue @@ -13,7 +13,7 @@
-
+
+
" + + " end - def create_contact_inbox + def create_contact_inbox(additional_attributes = {}) ActiveRecord::Base.transaction do - contact = inbox.account.contacts.create!(name: ::Haikunator.haikunate(1000)) + contact = inbox.account.contacts.create!( + name: ::Haikunator.haikunate(1000), + additional_attributes: additional_attributes + ) contact_inbox = ::ContactInbox.create!( contact_id: contact.id, inbox_id: inbox.id, diff --git a/app/models/concerns/sso_authenticatable.rb b/app/models/concerns/sso_authenticatable.rb new file mode 100644 index 00000000000..aca28b66333 --- /dev/null +++ b/app/models/concerns/sso_authenticatable.rb @@ -0,0 +1,23 @@ +module SsoAuthenticatable + extend ActiveSupport::Concern + + def generate_sso_auth_token + token = SecureRandom.hex(32) + ::Redis::Alfred.setex(sso_token_key(token), true, 5.minutes) + token + end + + def invalidate_sso_auth_token(token) + ::Redis::Alfred.delete(sso_token_key(token)) + end + + def valid_sso_auth_token?(token) + ::Redis::Alfred.get(sso_token_key(token)).present? + end + + private + + def sso_token_key(token) + format(::Redis::RedisKeys::USER_SSO_AUTH_TOKEN, user_id: id, token: token) + end +end diff --git a/app/models/contact.rb b/app/models/contact.rb index c6f6a00a408..82ece117645 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -38,9 +38,8 @@ class Contact < ApplicationRecord has_many :messages, as: :sender, dependent: :destroy before_validation :prepare_email_attribute - after_create_commit :dispatch_create_event + after_create_commit :dispatch_create_event, :ip_lookup after_update_commit :dispatch_update_event - after_commit :ip_lookup def get_source_id(inbox_id) contact_inboxes.find_by!(inbox_id: inbox_id).source_id diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 53e30daeab6..3a90d1f823c 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -98,7 +98,7 @@ class Conversation < ApplicationRecord end def muted? - !Redis::Alfred.get(mute_key).nil? + Redis::Alfred.get(mute_key).present? end def lock! @@ -287,7 +287,7 @@ class Conversation < ApplicationRecord end def mute_key - format('CONVERSATION::%d::MUTED', id: id) + format(Redis::RedisKeys::CONVERSATION_MUTE_KEY, id: id) end def mute_period diff --git a/app/models/integrations/app.rb b/app/models/integrations/app.rb index f5e783f0421..7861cc5c826 100644 --- a/app/models/integrations/app.rb +++ b/app/models/integrations/app.rb @@ -10,11 +10,11 @@ class Integrations::App end def name - params[:name] + I18n.t("integration_apps.#{params[:i18n_key]}.name") end def description - params[:description] + I18n.t("integration_apps.#{params[:i18n_key]}.description") end def logo diff --git a/app/models/user.rb b/app/models/user.rb index 88c9064316a..19b17731c8c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -44,6 +44,7 @@ class User < ApplicationRecord include Pubsubable include Rails.application.routes.url_helpers include Reportable + include SsoAuthenticatable devise :database_authenticatable, :registerable, diff --git a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder index 729b52379ee..517f310543c 100644 --- a/app/views/api/v1/conversations/partials/_conversation.json.jbuilder +++ b/app/views/api/v1/conversations/partials/_conversation.json.jbuilder @@ -3,7 +3,7 @@ json.meta do json.partial! 'api/v1/models/contact.json.jbuilder', resource: conversation.contact end json.channel conversation.inbox.try(:channel_type) - if conversation.assignee + if conversation.assignee&.account json.assignee do json.partial! 'api/v1/models/agent.json.jbuilder', resource: conversation.assignee end @@ -14,7 +14,7 @@ json.id conversation.display_id if conversation.unread_incoming_messages.count.zero? json.messages [conversation.messages.includes([{ attachments: [{ file_attachment: [:blob] }] }]).last.try(:push_event_data)] else - json.messages conversation.unread_messages.includes([:user, { attachments: [{ file_attachment: [:blob] }] }]).map(&:push_event_data) + json.messages conversation.unread_messages.includes([:user, { attachments: [{ file_attachment: [:blob] }] }]).last(10).map(&:push_event_data) end json.inbox_id conversation.inbox_id diff --git a/app/views/api/v2/accounts/reports/agents.csv.erb b/app/views/api/v2/accounts/reports/agents.csv.erb index 2986c6491a2..3675c0a6602 100644 --- a/app/views/api/v2/accounts/reports/agents.csv.erb +++ b/app/views/api/v2/accounts/reports/agents.csv.erb @@ -8,5 +8,6 @@ until: params[:until] }).summary %> <% row = [ agent.name, agent_report[:conversations_count], (agent_report[:avg_first_response_time]/60).to_i, (agent_report[:avg_resolution_time]/60).to_i ] %> -<%= CSV.generate_line row %> -<% end %> \ No newline at end of file + <%= CSV.generate_line row %> +<% end %> +<%= CSV.generate_line [I18n.t('reports.period', { since: Date.strptime(params[:since], '%s'), until: Date.strptime(params[:until], '%s') })] %> diff --git a/app/views/layouts/vueapp.html.erb b/app/views/layouts/vueapp.html.erb index c2dae52794a..40d9b7a1bbe 100644 --- a/app/views/layouts/vueapp.html.erb +++ b/app/views/layouts/vueapp.html.erb @@ -35,7 +35,8 @@ <% if ENV['VAPID_PUBLIC_KEY'] %> vapidPublicKey: new Uint8Array(<%= Base64.urlsafe_decode64(ENV['VAPID_PUBLIC_KEY']).bytes %>), <% end %> - enabledLanguages: <%= available_locales_with_name.to_json.html_safe %> + enabledLanguages: <%= available_locales_with_name.to_json.html_safe %>, + selectedLocale: '<%= I18n.locale %>' } window.globalConfig = <%= raw @global_config.to_json %> diff --git a/config/app.yml b/config/app.yml index c6fcd07fc06..c360ac4ddd4 100644 --- a/config/app.yml +++ b/config/app.yml @@ -1,5 +1,5 @@ shared: &shared - version: '1.10.0' + version: '1.11.0' development: <<: *shared diff --git a/config/initializers/languages.rb b/config/initializers/languages.rb index 1b0e3266244..92ab876e021 100644 --- a/config/initializers/languages.rb +++ b/config/initializers/languages.rb @@ -10,7 +10,7 @@ LANGUAGES_CONFIG = { 5 => { name: 'हिन्दी (hi)', iso_639_3_code: 'hin', iso_639_1_code: 'hi', enabled: false }, 6 => { name: 'Italiano (it)', iso_639_3_code: 'ita', iso_639_1_code: 'it', enabled: true }, 7 => { name: '日本語 (ja)', iso_639_3_code: 'jpn', iso_639_1_code: 'ja', enabled: true }, - 8 => { name: '한국어 (ko)', iso_639_3_code: 'kor', iso_639_1_code: 'ko', enabled: false }, + 8 => { name: '한국어 (ko)', iso_639_3_code: 'kor', iso_639_1_code: 'ko', enabled: true }, 9 => { name: 'Português (pt)', iso_639_3_code: 'por', iso_639_1_code: 'pt', enabled: true }, 10 => { name: 'русский (ru)', iso_639_3_code: 'rus', iso_639_1_code: 'ru', enabled: true }, 11 => { name: '中文 (zh)', iso_639_3_code: 'zho', iso_639_1_code: 'zh', enabled: false }, @@ -23,7 +23,12 @@ LANGUAGES_CONFIG = { 18 => { name: 'தமிழ் (ta)', iso_639_3_code: 'tam', iso_639_1_code: 'ta', enabled: true }, 19 => { name: 'فارسی (fa)', iso_639_3_code: 'fas', iso_639_1_code: 'fa', enabled: true }, 20 => { name: '中文 (台湾) (zh-TW)', iso_639_3_code: 'zho', iso_639_1_code: 'zh_TW', enabled: true }, - 21 => { name: 'Tiếng Việt (vi)', iso_639_3_code: 'vie', iso_639_1_code: 'vi', enabled: true } + 21 => { name: 'Tiếng Việt (vi)', iso_639_3_code: 'vie', iso_639_1_code: 'vi', enabled: true }, + 22 => { name: 'dansk (da)', iso_639_3_code: 'dan', iso_639_1_code: 'da', enabled: true }, + 23 => { name: 'Türkçe (tr)', iso_639_3_code: 'tur', iso_639_1_code: 'tr', enabled: true }, + 24 => { name: 'čeština (cs)', iso_639_3_code: 'ces', iso_639_1_code: 'cs', enabled: true }, + 25 => { name: 'suomi, suomen kieli (fi)', iso_639_3_code: 'fin', iso_639_1_code: 'fi', enabled: true }, + 26 => { name: 'Bahasa Indonesia (id)', iso_639_3_code: 'ind', iso_639_1_code: 'id', enabled: true } }.filter { |_key, val| val[:enabled] }.freeze Rails.configuration.i18n.available_locales = LANGUAGES_CONFIG.map { |_index, lang| lang[:iso_639_1_code].to_sym } diff --git a/config/integration/apps.yml b/config/integration/apps.yml index e6b3496984b..d58254bdedf 100644 --- a/config/integration/apps.yml +++ b/config/integration/apps.yml @@ -1,12 +1,10 @@ slack: id: slack - name: Slack logo: slack.png - description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + i18n_key: slack action: https://slack.com/oauth/v2/authorize?scope=commands,chat:write,channels:read,channels:manage,channels:join,groups:write,im:write,mpim:write,users:read,users:read.email,chat:write.customize,channels:history,groups:history,mpim:history,im:history webhooks: id: webhook - name: Webhooks logo: cable.svg - description: Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks. + i18n_key: webhooks action: /webhook diff --git a/config/locales/ar.yml b/config/locales/ar.yml index df6ec91822f..6e2e8e5eb57 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -27,6 +27,8 @@ ar: invalid_email: لقد قمت بإدخال عنوان بريد إلكتروني غير صالح email_already_exists: "لقد قمت بالفعل بتسجيل حساب سابقاً بالعنوان %{email}" failed: فشلت عملية التسجيل + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ ar: reply: email_subject: "رسائل جديدة في هذه المحادثة" transcript_subject: "نص المحادثة" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/ca.yml b/config/locales/ca.yml index 1db2a51c98a..55ca5653e10 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -27,6 +27,8 @@ ca: invalid_email: Heu introduït un correu electrònic no vàlid email_already_exists: "Ja us heu registrat amb el compte %{email}" failed: El registre ha fallat + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ ca: reply: email_subject: "Missatges nous en aquesta conversa" transcript_subject: "Transcripció de conversa" + integration_apps: + slack: + name: "Slack" + description: "Slack és una eina de xat que uneix tota la teva comunicació en un sol lloc. Integrant Slack, podràs rebre notificacions de totes les noves conversacions a la teva compte directament a dins l'Slack." + webhooks: + name: "Webhooks" + description: "Els esdeveniments de Webhook us proporcionen informació en temps real sobre el que passa al vostre compte de Chatwoot. Podeu utilitzar els webhooks per comunicar els esdeveniments a les vostres aplicacions preferides com Slack o Github. Feu clic a Configura per configurar els enllaços web." diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 4c230eb4ad1..992b98b1d15 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -27,6 +27,8 @@ cs: invalid_email: Zadali jste neplatný e-mail email_already_exists: "Již jste se zaregistrovali k účtu s %{email}" failed: Registrace se nezdařila + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ cs: reply: email_subject: "New messages on this conversation" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/da.yml b/config/locales/da.yml index 0b1d69118ca..144259ddaa9 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -17,35 +17,44 @@ #To learn more, please read the Rails Internationalization guide #available at https://guides.rubyonrails.org/i18n.html. da: - hello: "Hello world" + hello: "Hej verden" messages: - reset_password_success: Woot! Request for password reset is successful. Check your mail for instructions. - reset_password_failure: Uh ho! We could not find any user with the specified email. + reset_password_success: Woot! Anmodning om nulstilling af adgangskode er vellykket. Tjek din mail for instruktioner. + reset_password_failure: Åh nej! Vi kunne ikke finde nogen bruger med den angivne e-mail. errors: signup: - disposable_email: We do not allow disposable emails - invalid_email: You have entered an invalid email - email_already_exists: "You have already signed up for an account with %{email}" - failed: Signup failed + disposable_email: Vi tillader ikke engangs e-mails + invalid_email: Du har indtastet en ugyldig e-mail + email_already_exists: "Du har allerede tilmeldt dig en konto med %{email}" + failed: Tilmelding mislykkedes + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: - resolved: "Conversation was marked resolved by %{user_name}" - open: "Conversation was reopened by %{user_name}" - auto_resolved: "Conversation was marked resolved by system due to %{duration} days of inactivity" + resolved: "Samtalen blev markeret som løst af %{user_name}" + open: "Samtalen blev genåbnet af %{user_name}" + auto_resolved: "Samtalen blev markeret som løst af systemet på grund af %{duration} dages inaktivitet" assignee: - self_assigned: "%{user_name} self-assigned this conversation" - assigned: "Assigned to %{assignee_name} by %{user_name}" - removed: "Conversation unassigned by %{user_name}" + self_assigned: "%{user_name} selv-tildelte denne samtale" + assigned: "Tildelt %{assignee_name} af %{user_name}" + removed: "Samtale fjernet tildeling af %{user_name}" labels: - added: "%{user_name} added %{labels}" - removed: "%{user_name} removed %{labels}" - muted: "%{user_name} has muted the conversation" - unmuted: "%{user_name} has unmuted the conversation" + added: "%{user_name} tilføjede %{labels}" + removed: "%{user_name} fjernede %{labels}" + muted: "%{user_name} har slukket for samtalen" + unmuted: "%{user_name} har genaktiveret samtalen" templates: - greeting_message_body: "%{account_name} typically replies in a few hours." - ways_to_reach_you_message_body: "Give the team a way to reach you." - email_input_box_message_body: "Get notified by email" + greeting_message_body: "%{account_name} svarer typisk på et par timer." + ways_to_reach_you_message_body: "Giv teamet en måde at kontakte dig på." + email_input_box_message_body: "Få besked via e-mail" reply: - email_subject: "New messages on this conversation" - transcript_subject: "Conversation Transcript" + email_subject: "Nye beskeder i denne samtale" + transcript_subject: "Samtaleudskrift" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/de.yml b/config/locales/de.yml index 8af185395ee..a5e71bd864b 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -27,6 +27,8 @@ de: invalid_email: Sie haben eine ungültige E-Mail-Adresse eingegeben email_already_exists: "Sie haben sich bereits für ein Konto bei %{email} angemeldet." failed: Anmeldung gescheitert + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ de: reply: email_subject: "New messages on this conversation" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/devise.da.yml b/config/locales/devise.da.yml index fe5fb7196fb..dfa9a8613b1 100644 --- a/config/locales/devise.da.yml +++ b/config/locales/devise.da.yml @@ -2,60 +2,60 @@ da: devise: confirmations: - confirmed: "Your email address has been successfully confirmed." - send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." - send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." + confirmed: "Din e-mailadresse er blevet bekræftet." + send_instructions: "Du vil modtage en e-mail med instruktioner til, hvordan du bekræfter din e-mail-adresse om et par minutter." + send_paranoid_instructions: "Hvis din e-mailadresse findes i vores database, du vil modtage en e-mail med instruktioner til, hvordan du bekræfter din e-mail-adresse om et par minutter." failure: - already_authenticated: "You are already signed in." - inactive: "Your account is not activated yet." - invalid: "Invalid %{authentication_keys}/password or account is not verified yet." - locked: "Your account is locked." - last_attempt: "You have one more attempt before your account is locked." - not_found_in_database: "Invalid %{authentication_keys} or password." - timeout: "Your session expired. Please sign in again to continue." - unauthenticated: "You need to sign in or sign up before continuing." - unconfirmed: "You have to confirm your email address before continuing." + already_authenticated: "Du er allerede logget ind." + inactive: "Din konto er ikke aktiveret endnu." + invalid: "Ugyldig %{authentication_keys}/password eller konto er ikke bekræftet endnu." + locked: "Din konto er låst." + last_attempt: "Du har et forsøg mere, før din konto er låst." + not_found_in_database: "Ugyldig %{authentication_keys} eller adgangskode." + timeout: "Din session er udløbet. Log ind igen for at fortsætte." + unauthenticated: "Du skal logge ind eller tilmelde dig, før du fortsætter." + unconfirmed: "Du skal bekræfte din e-mailadresse, før du fortsætter." mailer: confirmation_instructions: - subject: "Confirmation Instructions" + subject: "Bekræftelsesinstruktioner" reset_password_instructions: - subject: "Reset password instructions" + subject: "Nulstil adgangskodeinstruktioner" unlock_instructions: - subject: "Unlock instructions" + subject: "Lås instruktioner op" password_change: - subject: "Password Changed" + subject: "Adgangskode Ændret" omniauth_callbacks: - failure: "Could not authenticate you from %{kind} because \"%{reason}\"." - success: "Successfully authenticated from %{kind} account." + failure: "Kunne ikke godkende dig fra %{kind} fordi \"%{reason}\"." + success: "Godkendt fra %{kind} -kontoen." passwords: - no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." - send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." - send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." - updated: "Your password has been changed successfully. You are now signed in." - updated_not_active: "Your password has been changed successfully." + no_token: "Du kan ikke få adgang til denne side uden at komme fra en e-mailen til nulstilling af adgangskoden. Hvis du kommer fra en e-mail til nulstilling af adgangskoden, skal du sørge for at bruge den fulde URL, der er angivet." + send_instructions: "Du vil om få minutter modtage en e-mail med instruktioner om, hvordan du nulstiller din adgangskode." + send_paranoid_instructions: "Hvis din e-mailadresse findes i vores database, vil du om få minutter modtage et link til genoprettelse af adgangskoden på din e-mailadresse." + updated: "Din adgangskode er blevet ændret. Du er nu logget ind." + updated_not_active: "Din adgangskode er blevet ændret." registrations: - destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." - signed_up: "Welcome! You have signed up successfully." - signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." - signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." - signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." - update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." - updated: "Your account has been updated successfully." + destroyed: "Farvel! Din konto er blevet annulleret. Vi håber snart at se dig igen." + signed_up: "Velkommen! Du er nu tilmeldt." + signed_up_but_inactive: "Du har tilmeldt dig. Vi kunne dog ikke logge dig på, fordi din konto endnu ikke er aktiveret." + signed_up_but_locked: "Du har tilmeldt dig. Vi kunne dog ikke logge dig ind, fordi din konto er låst." + signed_up_but_unconfirmed: "En besked med et bekræftelseslink er blevet sendt til din e-mailadresse. Følg linket for at aktivere din konto." + update_needs_confirmation: "Din konto er opdateret, men vi er nødt til at bekræfte din nye e-mailadresse. Tjek venligst din e-mail og følg linket for at bekræfte din nye e-mailadresse." + updated: "Din konto er blevet opdateret." sessions: - signed_in: "Signed in successfully." - signed_out: "Signed out successfully." - already_signed_out: "Signed out successfully." + signed_in: "Logget ind." + signed_out: "Logget ud." + already_signed_out: "Logget ud." unlocks: - send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." - send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." - unlocked: "Your account has been unlocked successfully. Please sign in to continue." + send_instructions: "Du vil modtage en e-mail med instruktioner til, hvordan du låser din konto op om et par minutter." + send_paranoid_instructions: "Hvis din konto findes, vil du modtage en e-mail med instruktioner til, hvordan du låser den op om et par minutter." + unlocked: "Din konto er blevet låst op. Log venligst ind for at fortsætte." errors: messages: - already_confirmed: "was already confirmed, please try signing in" - confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" - expired: "has expired, please request a new one" - not_found: "not found" - not_locked: "was not locked" + already_confirmed: "var allerede bekræftet, prøv venligst at logge ind" + confirmation_period_expired: "skal bekræftes inden for %{period}, anmod venligst om en ny" + expired: "er udløbet, anmod venligst om en ny" + not_found: "ikke fundet" + not_locked: "var ikke låst" not_saved: - one: "1 error prohibited this %{resource} from being saved:" - other: "%{count} errors prohibited this %{resource} from being saved:" + one: "1 fejl forhindrede denne %{resource} i at blive gemt:" + other: "%{count} fejl forhindrede denne %{resource} i at blive gemt:" diff --git a/config/locales/devise.fi.yml b/config/locales/devise.fi.yml index e2aa3482952..9a6b213b59b 100644 --- a/config/locales/devise.fi.yml +++ b/config/locales/devise.fi.yml @@ -2,60 +2,60 @@ fi: devise: confirmations: - confirmed: "Your email address has been successfully confirmed." - send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." - send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." + confirmed: "Sähköpostiosoitteesi on vahvistettu onnistuneesti." + send_instructions: "Saat sähköpostiviestin, jossa on ohjeet siitä, miten voit vahvistaa sähköpostiosoitteesi muutamassa minuutissa." + send_paranoid_instructions: "Jos sähköpostiosoitteesi on olemassa tietokannassamme, saat sähköpostiviestin, jossa on ohjeet siitä, miten voit vahvistaa sähköpostiosoitteesi muutamassa minuutissa." failure: - already_authenticated: "You are already signed in." - inactive: "Your account is not activated yet." - invalid: "Invalid %{authentication_keys}/password or account is not verified yet." - locked: "Your account is locked." - last_attempt: "You have one more attempt before your account is locked." - not_found_in_database: "Invalid %{authentication_keys} or password." - timeout: "Your session expired. Please sign in again to continue." - unauthenticated: "You need to sign in or sign up before continuing." - unconfirmed: "You have to confirm your email address before continuing." + already_authenticated: "Olet jo kirjautunut sisään." + inactive: "Tilisi ei ole vielä aktivoitu." + invalid: "Virheellinen %{authentication_keys}/salasana tai tiliä ei ole vielä vahvistettu." + locked: "Tilisi on lukittu." + last_attempt: "Sinulla on vielä yksi yritys ennen kuin tilisi lukitaan." + not_found_in_database: "Virheellinen %{authentication_keys} tai salasana." + timeout: "Istuntosi on vanhentunut. Ole hyvä ja kirjaudu sisään jatkaaksesi." + unauthenticated: "Sinun täytyy kirjautua sisään tai rekisteröityä ennen jatkamista." + unconfirmed: "Sinun on vahvistettava sähköpostiosoitteesi ennen jatkamista." mailer: confirmation_instructions: - subject: "Confirmation Instructions" + subject: "Vahvistusohjeet" reset_password_instructions: - subject: "Reset password instructions" + subject: "Salasanan nollausohjeet" unlock_instructions: - subject: "Unlock instructions" + subject: "Avausohjeet" password_change: - subject: "Password Changed" + subject: "Salasana vaihdettu" omniauth_callbacks: - failure: "Could not authenticate you from %{kind} because \"%{reason}\"." - success: "Successfully authenticated from %{kind} account." + failure: "Sinua ei voitu todentaa %{kind}-tililtä koska \"%{reason}\"." + success: "Onnistuneesti todennettu %{kind}-tililtä." passwords: - no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." - send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." - send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." - updated: "Your password has been changed successfully. You are now signed in." - updated_not_active: "Your password has been changed successfully." + no_token: "Et voi käyttää tätä sivua ilman salasanan nollaussähköpostia. Jos tulet sivulle sähköpostin linkin kautta, varmista, että olet käyttänyt koko URL-osoite." + send_instructions: "Saat sähköpostiviestin, jossa on ohjeet siitä, miten salasanasi palautetaan muutamassa minuutissa." + send_paranoid_instructions: "Jos sähköpostiosoitteesi on olemassa tietokannassamme, saat sähköpostiviestin, jossa on ohjeet siitä, miten voit vaihtaa salasanan muutamassa minuutissa." + updated: "Salasanasi on vaihdettu onnistuneesti. Olet nyt kirjautunut sisään." + updated_not_active: "Salasanasi on vaihdettu onnistuneesti." registrations: - destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." - signed_up: "Welcome! You have signed up successfully." - signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." - signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." - signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." - update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." - updated: "Your account has been updated successfully." + destroyed: "Heippa! Tilisi on peruutettu onnistuneesti. Toivomme näkevämme sinut pian uudelleen." + signed_up: "Tervetuloa! Olet rekisteröitynyt onnistuneesti." + signed_up_but_inactive: "Olet rekisteröitynyt onnistuneesti. Emme kuitenkaan voineet kirjata sinua sisään, koska tilisi ei ole vielä aktivoitu." + signed_up_but_locked: "Olet rekisteröitynyt onnistuneesti. Emme kuitenkaan voineet kirjata sinua sisään, koska tilisi on lukittu." + signed_up_but_unconfirmed: "Sähköpostiosoitteeseesi on lähetetty viesti, jossa on vahvistuslinkki. Ole hyvä ja seuraa linkkiä aktivoidaksesi tilisi." + update_needs_confirmation: "Olet päivittänyt tilisi onnistuneesti, mutta meidän on vahvistettava uusi sähköpostiosoitteesi. Ole hyvä ja tarkista sähköpostisi ja seuraa vahvistuslinkkiä vahvistaaksesi uuden sähköpostiosoitteesi." + updated: "Tilisi on päivitetty onnistuneesti." sessions: - signed_in: "Signed in successfully." - signed_out: "Signed out successfully." - already_signed_out: "Signed out successfully." + signed_in: "Kirjauduttu sisään onnistuneesti." + signed_out: "Kirjauduttu ulos onnistuneesti." + already_signed_out: "Kirjauduttu ulos onnistuneesti." unlocks: - send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." - send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." - unlocked: "Your account has been unlocked successfully. Please sign in to continue." + send_instructions: "Saat sähköpostiviestin, jossa on ohjeet siitä, miten tilin lukitus avataan muutamassa minuutissa." + send_paranoid_instructions: "Jos tilisi on olemassa, saat sähköpostiviestin, jossa on ohjeet sen avaamisesta muutamassa minuutissa." + unlocked: "Tilisi lukitus on poistettu onnistuneesti. Kirjaudu sisään jatkaaksesi." errors: messages: - already_confirmed: "was already confirmed, please try signing in" - confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" - expired: "has expired, please request a new one" - not_found: "not found" - not_locked: "was not locked" + already_confirmed: "on jo vahvistettu, yritä kirjautua sisään" + confirmation_period_expired: "täytyy vahvistaa %{period} päivän sisällä, pyydä uusi versio" + expired: "on vanhentunut, pyydä uusi" + not_found: "ei löydy" + not_locked: "ei ollut lukittu" not_saved: - one: "1 error prohibited this %{resource} from being saved:" - other: "%{count} errors prohibited this %{resource} from being saved:" + one: "1 virhe esti tämän %{resource} tallennuksen:" + other: "%{count} virhettä esti tämän %{resource} tallennuksen:" diff --git a/config/locales/devise.id.yml b/config/locales/devise.id.yml new file mode 100644 index 00000000000..fc6bfb26ae9 --- /dev/null +++ b/config/locales/devise.id.yml @@ -0,0 +1,60 @@ +#Additional translations at https://github.com/plataformatec/devise/wiki/I18n +id: + devise: + confirmations: + confirmed: "Alamat email Anda telah berhasil dikonfirmasi." + send_instructions: "Anda akan menerima email dengan instruksi bagaimana untuk mengkonfirmasi alamat email Anda dalam beberapa menit." + send_paranoid_instructions: "Jika alamat email Anda ada di database kami, Anda akan menerima email dengan instruksi bagaimana untuk mengkonfirmasi alamat email Anda dalam beberapa menit." + failure: + already_authenticated: "Anda berhasil masuk." + inactive: "Akun Anda belum diaktifkan." + invalid: "%{authentication_keys} / password atau akun tidak valid." + locked: "Akun Anda terkunci." + last_attempt: "Anda memiliki satu kesempatan lagi sebelum akun Anda dikunci." + not_found_in_database: "%{authentication_keys} atau password tidak valid." + timeout: "Sesi Anda kadalwarsa. Harap masuk lagi untuk melanjutkan." + unauthenticated: "Anda harus masuk atau mendaftar sebelum melanjutkan." + unconfirmed: "Anda harus mengkonfirmasi alamat email Anda sebelum melanjutkan." + mailer: + confirmation_instructions: + subject: "Petunjuk Konfirmasi" + reset_password_instructions: + subject: "Instruksi ubah kata sandi" + unlock_instructions: + subject: "Petunjuk unlock" + password_change: + subject: "Kata Sandi Diubah" + omniauth_callbacks: + failure: "Tidak dapat mengautentikasi Anda dari %{kind} karena \"%{reason}\"." + success: "Berhasil diautentikasi dari akun %{kind}." + passwords: + no_token: "Anda tidak dapat mengakses halaman ini tanpa datang dari email pengaturan ulang kata sandi. Jika Anda memang datang dari email pengaturan ulang kata sandi, harap pastikan Anda menggunakan URL lengkap yang telah disediakan." + send_instructions: "Anda akan menerima email dengan instruksi tentang cara mengatur ulang kata sandi Anda dalam beberapa menit." + send_paranoid_instructions: "Jika alamat email Anda ada di database kami, Anda akan menerima tautan pemulihan kata sandi di alamat email Anda dalam beberapa menit." + updated: "Kata sandi Anda telah berhasil diubah. Anda sekarang sudah masuk." + updated_not_active: "Kata sandi Anda berhasil diubah." + registrations: + destroyed: "Selamat tinggal! Akun Anda telah berhasil dibatalkan. Kami berharap dapat bertemu Anda lagi segera." + signed_up: "Selamat datang! Anda berhasil mendaftar." + signed_up_but_inactive: "Anda berhasil mendaftar. Namun, Anda tidak dapat masuk karena akun Anda belum diaktifkan." + signed_up_but_locked: "Anda berhasil mendaftar. Namun, Anda tidak dapat masuk karena akun Anda terkunci." + signed_up_but_unconfirmed: "Sebuah pesan dengan link konfirmasi telah dikirim ke alamat email Anda. Silakan ikuti link untuk mengaktifkan akun Anda." + update_needs_confirmation: "Anda berhasil memperbarui akun Anda, tetapi kami perlu memverifikasi alamat email baru Anda. Silakan periksa email Anda dan ikuti tautan konfirmasi untuk mengonfirmasi alamat email baru Anda." + updated: "Akun Anda berhasil diperbarui." + sessions: + signed_in: "Berhasil masuk." + signed_out: "Berhasil keluar." + already_signed_out: "Berhasil keluar." + unlocks: + send_instructions: "Anda akan menerima email dengan instruksi bagaimana membuka akun Anda dalam beberapa menit." + send_paranoid_instructions: "Jika akun Anda ada, Anda akan menerima email dengan instruksi tentang cara membukanya dalam beberapa menit." + unlocked: "Kunci akun Anda berhasil dibuka. Silahkan masuk untuk melanjutkan." + errors: + messages: + already_confirmed: "sudah dikonfirmasi, silakan coba masuk" + confirmation_period_expired: "perlu dikonfirmasi dalam %{period}, harap minta yang baru" + expired: "telah kedaluwarsa, silakan minta yang baru" + not_found: "tidak ditemukan" + not_locked: "tidak terkunci" + not_saved: + other: "%{count} kesalahan mengakibatkan %{resource} ini tidak dapat disimpan:" diff --git a/config/locales/devise.pt_BR.yml b/config/locales/devise.pt_BR.yml index bca956f73d9..3175718e3da 100644 --- a/config/locales/devise.pt_BR.yml +++ b/config/locales/devise.pt_BR.yml @@ -1,5 +1,5 @@ #Additional translations at https://github.com/plataformatec/devise/wiki/I18n -pt: +pt_BR: devise: confirmations: confirmed: "Seu e-mail foi confirmado com sucesso." diff --git a/config/locales/devise.tr.yml b/config/locales/devise.tr.yml index fae21f441f1..ef10b8d874e 100644 --- a/config/locales/devise.tr.yml +++ b/config/locales/devise.tr.yml @@ -2,60 +2,60 @@ tr: devise: confirmations: - confirmed: "Your email address has been successfully confirmed." - send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." - send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." + confirmed: "E-posta adresiniz başarıyla onaylanmıştır." + send_instructions: "Birkaç dakika içinde e-posta adresinizi nasıl onaylayacağınıza dair talimatları içeren bir e-posta alacaksınız." + send_paranoid_instructions: "E-posta adresiniz Veri tabanımızda mevcutsa, birkaç dakika içinde e-posta adresinizi nasıl onaylayacağınıza dair talimatları içeren bir e-posta alacaksınız." failure: - already_authenticated: "You are already signed in." - inactive: "Your account is not activated yet." - invalid: "Invalid %{authentication_keys}/password or account is not verified yet." - locked: "Your account is locked." - last_attempt: "You have one more attempt before your account is locked." - not_found_in_database: "Invalid %{authentication_keys} or password." - timeout: "Your session expired. Please sign in again to continue." - unauthenticated: "You need to sign in or sign up before continuing." - unconfirmed: "You have to confirm your email address before continuing." + already_authenticated: "Zaten oturum açmış durumdasınız." + inactive: "Hesabınız henüz aktifleştirilmedi." + invalid: "Geçersiz %{authentication_keys} / parola veya hesap henüz doğrulanmadı." + locked: "Hesabınız kilitlendi." + last_attempt: "Hesabınız kilitlenmeden önce bir deneme hakkınız daha var." + not_found_in_database: "Geçersiz %{authentication_keys} veya parola." + timeout: "Oturumunuzun süresi doldu. Devam etmek için lütfen tekrar oturum açın." + unauthenticated: "Devam etmeden önce oturum açmanız veya kaydolmanız gerekiyor." + unconfirmed: "Devam etmeden önce e-posta adresinizi onaylamalısınız." mailer: confirmation_instructions: - subject: "Confirmation Instructions" + subject: "Onay Talimatları" reset_password_instructions: - subject: "Reset password instructions" + subject: "Şifre talimatlarını sıfırla" unlock_instructions: - subject: "Unlock instructions" + subject: "Talimatların kilidini aç" password_change: - subject: "Password Changed" + subject: "Şifre değişti" omniauth_callbacks: - failure: "Could not authenticate you from %{kind} because \"%{reason}\"." - success: "Successfully authenticated from %{kind} account." + failure: "%{kind} tarafından kimliğiniz doğrulanamadı çünkü \"%{reason}\"." + success: "%{kind} hesabından başarıyla kimlik doğrulaması yapıldı." passwords: - no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." - send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." - send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." - updated: "Your password has been changed successfully. You are now signed in." - updated_not_active: "Your password has been changed successfully." + no_token: "Parola sıfırlama e-postasından gelmeden bu sayfaya erişemezsiniz. Bir şifre sıfırlama e-postasından geliyorsanız, lütfen sağlanan tam URL'yi kullandığınızdan emin olun." + send_instructions: "Birkaç dakika içinde şifrenizi nasıl sıfırlayacağınıza dair talimatları içeren bir e-posta alacaksınız." + send_paranoid_instructions: "E-posta adresiniz veritabanımızda mevcutsa, birkaç dakika içinde e-posta adresinize bir şifre kurtarma bağlantısı alacaksınız." + updated: "Şifreniz başarıyla değiştirildi. Şimdi giriş yaptınız." + updated_not_active: "Şifreniz başarıyla değiştirildi." registrations: - destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." - signed_up: "Welcome! You have signed up successfully." - signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." - signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." - signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." - update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." - updated: "Your account has been updated successfully." + destroyed: "Hoşçakal! Hesabınız başarıyla iptal edildi. Kısa süre sonra tekrar görüşmeyi umuyoruz." + signed_up: "Hoşgeldiniz! Başarıyla kaydoldunuz." + signed_up_but_inactive: "Başarıyla kaydoldunuz. Ancak, hesabınız henüz etkinleştirilmediğinden oturumunuzu açamadık." + signed_up_but_locked: "Başarıyla kaydoldunuz. Ancak, hesabınız kilitli olduğu için oturumunuzu açamadık." + signed_up_but_unconfirmed: "E-posta adresinize onay bağlantısı içeren bir mesaj gönderildi. Hesabınızı etkinleştirmek için lütfen bağlantıyı takip edin." + update_needs_confirmation: "Hesabınızı başarıyla güncellediniz, ancak yeni e-posta adresinizi doğrulamamız gerekiyor. Lütfen e-postanızı kontrol edin ve yeni e-posta adresinizi onaylamak için onay bağlantısını takip edin." + updated: "Hesabınız başarıyla güncellendi." sessions: - signed_in: "Signed in successfully." - signed_out: "Signed out successfully." - already_signed_out: "Signed out successfully." + signed_in: "Başarıyla oturum açıldı." + signed_out: "Başarıyla çıkış yapıldı." + already_signed_out: "Başarıyla çıkış yapıldı." unlocks: - send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." - send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." - unlocked: "Your account has been unlocked successfully. Please sign in to continue." + send_instructions: "Birkaç dakika içinde hesabınızın kilidini nasıl açacağınıza dair talimatları içeren bir e-posta alacaksınız." + send_paranoid_instructions: "Hesabınız varsa, birkaç dakika içinde kilidini nasıl açacağınıza dair talimatları içeren bir e-posta alacaksınız." + unlocked: "Hesabınızın kilidi başarıyla açıldı. Devam etmek için giriş yapın." errors: messages: - already_confirmed: "was already confirmed, please try signing in" - confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" - expired: "has expired, please request a new one" - not_found: "not found" - not_locked: "was not locked" + already_confirmed: "zaten onaylandı, lütfen oturum açmayı deneyin" + confirmation_period_expired: "%{period} içinde onaylanması gerekiyor, lütfen yeni bir tane isteyin" + expired: "süresi doldu, lütfen yeni bir tane isteyin" + not_found: "bulunamadı" + not_locked: "kilitli değil" not_saved: - one: "1 error prohibited this %{resource} from being saved:" - other: "%{count} errors prohibited this %{resource} from being saved:" + one: "1 hata bu %{resource} 'un kaydedilmesini engelledi:" + other: "%{count} hatası, bu %{resource} kaynağının kaydedilmesini yasakladı:" diff --git a/config/locales/devise.zh_CN.yml b/config/locales/devise.zh_CN.yml index f8613c84ced..2bf2831a887 100644 --- a/config/locales/devise.zh_CN.yml +++ b/config/locales/devise.zh_CN.yml @@ -1,5 +1,5 @@ #Additional translations at https://github.com/plataformatec/devise/wiki/I18n -zh-CN: +zh_CN: devise: confirmations: confirmed: "您的电子邮件地址已成功确认。" diff --git a/config/locales/devise.zh_TW.yml b/config/locales/devise.zh_TW.yml index d31370d3bd3..f892bf796a5 100644 --- a/config/locales/devise.zh_TW.yml +++ b/config/locales/devise.zh_TW.yml @@ -1,4 +1,4 @@ -# Additional translations at https://github.com/plataformatec/devise/wiki/I18n +#Additional translations at https://github.com/plataformatec/devise/wiki/I18n zh_TW: devise: confirmations: diff --git a/config/locales/el.yml b/config/locales/el.yml index 2a10147ab61..a40efd8e25a 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -27,6 +27,8 @@ el: invalid_email: Έχετε καταχωρήσει ακατάλληλο email email_already_exists: "Έχει ήδη καταχωρηθεί λογαριασμός στο %{email}" failed: Η εγγραφή απέτυχε + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ el: reply: email_subject: "νέα μηνύματα σε αυτήν την συνομιλία" transcript_subject: "Μεταγραφή Συνομιλίας" + integration_apps: + slack: + name: "Slack" + description: "Το Slack είναι ένα εργαλείο συνομιλιών (chat) το οποίο ενώνει σε ένα σημείο όλες τις επικοινωνίες. Ενοποιώντας με το Slack, μπορείτε να λαμβάνετε ειδοποιήσεις για όλες τις συνομιλίες στον λογαριασμό σας από το Slack." + webhooks: + name: "Webhooks" + description: "Τα συμβάντα Webhook μας εφοδιάζουν με πληροφορίες πραγματικού χρόνου σχετικά με το τι συμβαίνει στο λογαριασμό σας. Μπορείτε να χρησιμοποιήσετε τα webhooks για να μεταφέρετε τα συμβάντα σε άλλες εφαρμογές ή υπηρεσίες όπως το Slack ή Github. Πατήστε στην Διαμόρφωση για να ενημερώστε τα δικά σας webhooks." diff --git a/config/locales/en.yml b/config/locales/en.yml index e3b1db2c603..f21b162f9c9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -42,6 +42,9 @@ en: email_already_exists: "You have already signed up for an account with %{email}" failed: Signup failed + reports: + period: Reporting period %{since} to %{until} + conversations: activity: status: @@ -64,3 +67,11 @@ en: reply: email_subject: "New messages on this conversation" transcript_subject: "Conversation Transcript" + + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/es.yml b/config/locales/es.yml index d6cbca9af88..a8832a6efbc 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -27,6 +27,8 @@ es: invalid_email: Ha introducido un correo electrónico no válido email_already_exists: "Ya te has registrado en una cuenta con %{email}" failed: Registro fallido + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ es: reply: email_subject: "Nuevos mensajes en esta conversación" transcript_subject: "Transcripción de la conversación" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/fa.yml b/config/locales/fa.yml index 26cd7ca1953..59d102334a9 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -27,6 +27,8 @@ fa: invalid_email: ایمیل وارد شده معتبر نیست email_already_exists: "قبلا کاربری با ایمیل %{email} ثبت نام کرده است." failed: ثبت نام ناموفق بود + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ fa: reply: email_subject: "پیام‌های جدید در این مکالمه" transcript_subject: "متن مکالمه" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 584d961fd59..c58ed7abcfe 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -17,35 +17,44 @@ #To learn more, please read the Rails Internationalization guide #available at https://guides.rubyonrails.org/i18n.html. fi: - hello: "Hello world" + hello: "Hei maailma" messages: - reset_password_success: Woot! Request for password reset is successful. Check your mail for instructions. - reset_password_failure: Uh ho! We could not find any user with the specified email. + reset_password_success: Woot! Salasanan nollauspyyntö onnistui. Tarkista sähköpostisi saadaksesi ohjeita. + reset_password_failure: Hö! Emme löytäneet yhtään käyttäjää määritellyllä sähköpostilla. errors: signup: - disposable_email: We do not allow disposable emails - invalid_email: You have entered an invalid email - email_already_exists: "You have already signed up for an account with %{email}" - failed: Signup failed + disposable_email: Emme salli kertakäyttöisiä sähköposteja + invalid_email: Olet syöttänyt virheellisen sähköpostin + email_already_exists: "Olet jo rekisteröitynyt tiliin sähköpostin %{email} kanssa" + failed: Rekisteröityminen epäonnistui + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: - resolved: "Conversation was marked resolved by %{user_name}" - open: "Conversation was reopened by %{user_name}" - auto_resolved: "Conversation was marked resolved by system due to %{duration} days of inactivity" + resolved: "%{user_name} merkitsi keskustelun ratkaistuksi" + open: "%{user_name} uudelleenavasi keskustelun" + auto_resolved: "Keskustelu merkittiin ratkaistuksi automaattisesti, sillä on kulunut %{duration} päivää viimeisestä viestistä" assignee: - self_assigned: "%{user_name} self-assigned this conversation" - assigned: "Assigned to %{assignee_name} by %{user_name}" - removed: "Conversation unassigned by %{user_name}" + self_assigned: "%{user_name} ilmottautui keskusteluun" + assigned: "Delegoitu edustajalle %{assignee_name} edustajan %{user_name} toimesta" + removed: "%{user_name} poistui keskustelusta" labels: - added: "%{user_name} added %{labels}" - removed: "%{user_name} removed %{labels}" - muted: "%{user_name} has muted the conversation" - unmuted: "%{user_name} has unmuted the conversation" + added: "%{user_name} lisäsi tunnisteet %{labels}" + removed: "%{user_name} poisti tunnisteet %{labels}" + muted: "%{user_name} mykisti keskustelun" + unmuted: "%{user_name} poisti keskustelun mykistyksen" templates: - greeting_message_body: "%{account_name} typically replies in a few hours." - ways_to_reach_you_message_body: "Give the team a way to reach you." - email_input_box_message_body: "Get notified by email" + greeting_message_body: "%{account_name} vastaa tyypillisesti muutamassa tunnissa." + ways_to_reach_you_message_body: "Anna tiimillemme tapa tavoittaa sinut." + email_input_box_message_body: "Saa ilmoitus sähköpostitse" reply: - email_subject: "New messages on this conversation" - transcript_subject: "Conversation Transcript" + email_subject: "Uusia viestejä" + transcript_subject: "Keskustelukopio" + integration_apps: + slack: + name: "Slack" + description: "Slack on chat-työkalu, joka tuo kaiken viestintäsi yhteen paikkaan. Integroimalla Slackin voit saada ilmoituksen kaikista uusista keskusteluista Slackin sisällä." + webhooks: + name: "Webhookit" + description: "Webhook-tapahtumat antavat sinulle reaaliaikaista tietoa siitä, mitä Chatwoot-tililläsi tapahtuu. Voit käyttää webhookeja ja välittää tapahtumat suosikkiohjelmillesi, kuten Slackiin tai Githubiiin. Klikkaa \"Määrittele\" määrittääksesi webhookisi." diff --git a/config/locales/fr.yml b/config/locales/fr.yml index dea756ed36c..4fcd33aadb7 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -27,6 +27,8 @@ fr: invalid_email: Vous avez entré un courriel non valide email_already_exists: "Vous avez déjà créé un compte avec %{email}" failed: L'inscription a échoué + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ fr: reply: email_subject: "Nouveaux messages dans cette conversation" transcript_subject: "Transcription de conversation" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/hi.yml b/config/locales/hi.yml index f630407e428..f6c69191d90 100644 --- a/config/locales/hi.yml +++ b/config/locales/hi.yml @@ -27,6 +27,8 @@ hi: invalid_email: You have entered an invalid email email_already_exists: "You have already signed up for an account with %{email}" failed: Signup failed + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ hi: reply: email_subject: "New messages on this conversation" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/hu.yml b/config/locales/hu.yml index 4b8d0bc4c63..d885ab2985f 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -27,6 +27,8 @@ hu: invalid_email: You have entered an invalid email email_already_exists: "You have already signed up for an account with %{email}" failed: Signup failed + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ hu: reply: email_subject: "New messages on this conversation" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/id.yml b/config/locales/id.yml new file mode 100644 index 00000000000..acfc2f7ffab --- /dev/null +++ b/config/locales/id.yml @@ -0,0 +1,60 @@ +#Files in the config/locales directory are used for internationalization +#and are automatically loaded by Rails. If you want to use locales other +#than English, add the necessary files in this directory. +#To use the locales, use `I18n.t`: +#I18n.t 'hello' +#In views, this is aliased to just `t`: +#<%= t('hello') %> +#To use a different locale, set it with `I18n.locale`: +#I18n.locale = :es +#This would use the information in config/locales/es.yml. +#The following keys must be escaped otherwise they will not be retrieved by +#the default I18n backend: +#true, false, on, off, yes, no +#Instead, surround them with single quotes. +#en: +#'true': 'foo' +#To learn more, please read the Rails Internationalization guide +#available at https://guides.rubyonrails.org/i18n.html. +id: + hello: "Hello world" + messages: + reset_password_success: Woot! Permintaan pengaturan ulang kata sandi berhasil. Periksa email Anda untuk mendapatkan petunjuk. + reset_password_failure: Aduh! Kami tidak dapat menemukan pengguna dengan email yang dimasukkan. + errors: + signup: + disposable_email: Kami tidak mengizinkan email sekali pakai + invalid_email: Anda telah memasukkan email yang tidak valid + email_already_exists: "Anda telah mendaftar untuk sebuah akun dengan %{email}" + failed: Pendaftaran gagal + reports: + period: Reporting period %{since} to %{until} + conversations: + activity: + status: + resolved: "Percakapan ditandai selesai oleh %{user_name}" + open: "Percakapan telah dibuka kembali oleh %{user_name}" + auto_resolved: "Percakapan ditandai terselesaikan oleh sistem karena tidak ada aktifitas dalam %{duration} hari" + assignee: + self_assigned: "%{user_name} menetapkan sendiri percakapan ini" + assigned: "Ditugaskan ke %{assignee_name} oleh %{user_name}" + removed: "Percakapan batal ditetapkan oleh %{user_name}" + labels: + added: "%{user_name} menambahkan %{labels}" + removed: "%{user_name} menghapus %{labels}" + muted: "%{user_name} me-mute percakapan" + unmuted: "%{user_name} telah un-mute percakapan" + templates: + greeting_message_body: "%{account_name} biasanya membalas dalam beberapa jam." + ways_to_reach_you_message_body: "Beri tim cara untuk menghubungi Anda." + email_input_box_message_body: "Dapatkan pemberitahuan melalui email" + reply: + email_subject: "Pesan baru pada percakapan ini" + transcript_subject: "Transkrip Percakapan" + integration_apps: + slack: + name: "Slack" + description: "Slack adalah alat obrolan yang menyatukan semua komunikasi Anda di satu tempat. Dengan mengintegrasikan Slack, Anda bisa mendapatkan pemberitahuan tentang semua percakapan baru di akun Anda tepat di dalam Slack Anda." + webhooks: + name: "Webhooks" + description: "Webhook event memberi Anda informasi realtime tentang apa yang terjadi di akun Anda. Anda dapat menggunakan webhook untuk mengkomunikasikan acara ke aplikasi favorit Anda seperti Slack atau GitHub. Klik Konfigurasi untuk mengatur webhook Anda." diff --git a/config/locales/it.yml b/config/locales/it.yml index d26fd0254e8..de8617b46e3 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -27,6 +27,8 @@ it: invalid_email: Hai inserito un'email non valida email_already_exists: "Ti sei già registrato per un account con %{email}" failed: Iscrizione fallita + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ it: reply: email_subject: "Nuovi messaggi in questa conversazione" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 7f3d6d89609..0dc2434fd10 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -27,6 +27,8 @@ ja: invalid_email: 無効なメールアドレスを入力しました。 email_already_exists: "あなたは既に %{email} でアカウントにサインアップしています" failed: サインアップに失敗しました + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ ja: reply: email_subject: "この会話に新着メッセージがあります" transcript_subject: "会話の記録" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/ko.yml b/config/locales/ko.yml index ce75fd92d0d..47771974963 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -27,6 +27,8 @@ ko: invalid_email: You have entered an invalid email email_already_exists: "You have already signed up for an account with %{email}" failed: Signup failed + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ ko: reply: email_subject: "New messages on this conversation" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/ml.yml b/config/locales/ml.yml index 2e4190331e3..c88aa6550e8 100644 --- a/config/locales/ml.yml +++ b/config/locales/ml.yml @@ -27,6 +27,8 @@ ml: invalid_email: നിങ്ങൾ ഒരു അസാധുവായ ഇമെയിൽ നൽകി email_already_exists: "നിങ്ങൾ ഇതിനകം ഈ %{email} ഉപയോഗിച്ചു ഒരു അക്കൗണ്ടിനായി സൈൻ അപ്പ് ചെയ്തു" failed: സൈനപ്പ് പരാജയപ്പെട്ടു + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ ml: reply: email_subject: "ഈ സംഭാഷണത്തിലെ പുതിയ സന്ദേശങ്ങൾ" transcript_subject: "സംഭാഷണ ട്രാൻസ്ക്രിപ്റ്റ്" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 28a79c4a035..83d4e57a108 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -27,6 +27,8 @@ nl: invalid_email: Je hebt een ongeldig e-mailadres ingevoerd email_already_exists: "Je hebt je al aangemeld voor een account bij %{email}" failed: Aanmelden mislukt + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ nl: reply: email_subject: "New messages on this conversation" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/pl.yml b/config/locales/pl.yml index f6ac67850bf..4a23b908242 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -27,6 +27,8 @@ pl: invalid_email: Wprowadzono nieprawidłowy adres e-mail email_already_exists: "Już zarejestrowałeś konto w %{email}" failed: Rejestracja nie powiodła się + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ pl: reply: email_subject: "New messages on this conversation" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/pt.yml b/config/locales/pt.yml index 78215e32172..5d7bb4dd282 100644 --- a/config/locales/pt.yml +++ b/config/locales/pt.yml @@ -27,6 +27,8 @@ pt: invalid_email: Você digitou um email inválido email_already_exists: "Você já se inscreveu para uma conta com %{email}" failed: Falha na inscrição + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ pt: reply: email_subject: "New messages on this conversation" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/pt_BR.yml b/config/locales/pt_BR.yml index a0dc7549760..0453ba86373 100644 --- a/config/locales/pt_BR.yml +++ b/config/locales/pt_BR.yml @@ -16,7 +16,7 @@ #'true': 'foo' #To learn more, please read the Rails Internationalization guide #available at https://guides.rubyonrails.org/i18n.html. -pt: +pt_BR: hello: "Olá, mundo" messages: reset_password_success: Legal! A solicitação de alteração de senha foi bem sucedida. Verifique seu e-mail para obter instruções. @@ -27,21 +27,23 @@ pt: invalid_email: Você digitou um email inválido email_already_exists: "Você já se cadastrou para uma conta com %{email}" failed: Registro falhou + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: resolved: "Conversa foi marcada como resolvida por %{user_name}" open: "Conversa foi reaberta por %{user_name}" - auto_resolved: "Conversation was marked resolved by system due to %{duration} days of inactivity" + auto_resolved: "Conversa foi marcada como resolvida pelo sistema por ter %{duration} dias de inatividade" assignee: - self_assigned: "%{user_name} self-assigned this conversation" + self_assigned: "%{user_name} atribuiu a si mesmo essa conversa" assigned: "Atribuído a %{assignee_name} por %{user_name}" removed: "Conversa não atribuída por %{user_name}" labels: - added: "%{user_name} added %{labels}" - removed: "%{user_name} removed %{labels}" - muted: "%{user_name} has muted the conversation" - unmuted: "%{user_name} has unmuted the conversation" + added: "%{user_name} adicionou %{labels}" + removed: "%{user_name} removeu %{labels}" + muted: "%{user_name} silenciou a conversa" + unmuted: "%{user_name} reativou a conversa" templates: greeting_message_body: "%{account_name} normalmente responde em algumas horas." ways_to_reach_you_message_body: "Informe uma forma para entrarmos em contato com você." @@ -49,3 +51,10 @@ pt: reply: email_subject: "Novas mensagens nesta conversa" transcript_subject: "Transcrição da conversa" + integration_apps: + slack: + name: "Slack" + description: "Slack é uma ferramenta que reúne todas as suas comunicações em um só lugar. Ao integrar o Slack, você pode ser notificado de todas as novas conversas da sua conta diretamente no seu Slack." + webhooks: + name: "Webhooks" + description: "Webhooks fornecem informações em tempo real sobre o que está acontecendo em sua conta. Você pode usar os webhooks para comunicar eventos com seus aplicativos favoritos como Slack ou Github. Clique em Configurar para configurar seus webhooks." diff --git a/config/locales/ro.yml b/config/locales/ro.yml index 2906e27e61c..95918b8ebba 100644 --- a/config/locales/ro.yml +++ b/config/locales/ro.yml @@ -27,6 +27,8 @@ ro: invalid_email: Ați introdus un e-mail invalid email_already_exists: "V-ați înregistrat deja cu un cont cu %{email}" failed: Înregistrare eșuată + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ ro: reply: email_subject: "Mesaje noi în această conversație" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/ru.yml b/config/locales/ru.yml index b774787d24b..8a38431c9df 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -27,6 +27,8 @@ ru: invalid_email: Вы ввели неверный email email_already_exists: "Вы уже зарегистрировались для учётной записи с %{email}" failed: Ошибка регистрации + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -38,8 +40,8 @@ ru: assigned: "%{user_name} назначил %{assignee_name} ответственным" removed: "Ответственный снят %{user_name}" labels: - added: "%{user_name} added %{labels}" - removed: "%{user_name} removed %{labels}" + added: "%{user_name} добавил %{labels}" + removed: "%{user_name} удалил %{labels}" muted: "%{user_name} has muted the conversation" unmuted: "%{user_name} has unmuted the conversation" templates: @@ -49,3 +51,10 @@ ru: reply: email_subject: "Новые сообщения в этом диалоге" transcript_subject: "Субтитры общения" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/sk.yml b/config/locales/sk.yml index b011ccfc623..a0a77e3e4c9 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -27,6 +27,8 @@ sk: invalid_email: You have entered an invalid email email_already_exists: "You have already signed up for an account with %{email}" failed: Signup failed + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ sk: reply: email_subject: "New messages on this conversation" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/sv.yml b/config/locales/sv.yml index 820b4b4d972..078d0baf656 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -27,6 +27,8 @@ sv: invalid_email: Du har angett en ogiltig e-postadress email_already_exists: "Du har redan registrerat dig för ett konto med %{email}" failed: Registrering misslyckades + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ sv: reply: email_subject: "New messages on this conversation" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/ta.yml b/config/locales/ta.yml index a42bfc15c06..bd1efbef0b2 100644 --- a/config/locales/ta.yml +++ b/config/locales/ta.yml @@ -27,6 +27,8 @@ ta: invalid_email: நீங்கள் தவறான ஈ-மெயிலை உள்ளிட்டுள்ளீர்கள் email_already_exists: "நீங்கள் ஏற்கனவே %{email} கொண்டு கணக்கிற்கு பதிவு செய்துள்ளீர்கள்" failed: உள்நுழையும் முயறிசி தோல்வி அடைந்துள்ளது + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ ta: reply: email_subject: "இந்த உரையாடலில் புதிய செய்திகள்" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/th.yml b/config/locales/th.yml index 91b8af59b99..0ebe59b749c 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -27,6 +27,8 @@ th: invalid_email: You have entered an invalid email email_already_exists: "You have already signed up for an account with %{email}" failed: Signup failed + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ th: reply: email_subject: "New messages on this conversation" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 09e8cd68564..0ca0df31315 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -17,35 +17,44 @@ #To learn more, please read the Rails Internationalization guide #available at https://guides.rubyonrails.org/i18n.html. tr: - hello: "Hello world" + hello: "Merhaba Dünya" messages: - reset_password_success: Woot! Request for password reset is successful. Check your mail for instructions. - reset_password_failure: Uh ho! We could not find any user with the specified email. + reset_password_success: Parola sıfırlama isteği başarılı. Talimatlar için postanızı kontrol edin. + reset_password_failure: Belirtilen e-postaya sahip herhangi bir kullanıcı bulamadık. errors: signup: - disposable_email: We do not allow disposable emails - invalid_email: You have entered an invalid email - email_already_exists: "You have already signed up for an account with %{email}" - failed: Signup failed + disposable_email: Tek kullanımlık e-postalara izin vermiyoruz + invalid_email: Geçersiz bir e-posta girdiniz + email_already_exists: "%{email} ile zaten bir hesaba kaydoldunuz" + failed: Kayıt başarısız oldu + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: - resolved: "Conversation was marked resolved by %{user_name}" - open: "Conversation was reopened by %{user_name}" - auto_resolved: "Conversation was marked resolved by system due to %{duration} days of inactivity" + resolved: "Görüşme %{user_name} tarafından çözüldü olarak işaretlendi" + open: "Görüşme %{user_name} tarafından çözüldü olarak işaretlendi" + auto_resolved: " %{duration} günlük hareketsizlik nedeniyle görüşme, sistem tarafından çözümlendi olarak işaretlendi" assignee: - self_assigned: "%{user_name} self-assigned this conversation" - assigned: "Assigned to %{assignee_name} by %{user_name}" - removed: "Conversation unassigned by %{user_name}" + self_assigned: "%{user_name} bu görüşmeyi kendisi atadı" + assigned: "%{user_name} tarafından %{assignee_name} adına atandı" + removed: "Görüşmenin ataması %{user_name} tarafından kaldırıldı" labels: - added: "%{user_name} added %{labels}" - removed: "%{user_name} removed %{labels}" - muted: "%{user_name} has muted the conversation" - unmuted: "%{user_name} has unmuted the conversation" + added: "%{user_name},%{labels} ekledi" + removed: "%{user_name} , %{labels} kaldırdı" + muted: "%{user_name}, sohbeti sessize aldı" + unmuted: "%{user_name} görüşmenin sesini açtı" templates: - greeting_message_body: "%{account_name} typically replies in a few hours." - ways_to_reach_you_message_body: "Give the team a way to reach you." - email_input_box_message_body: "Get notified by email" + greeting_message_body: "%{account_name} genellikle birkaç saat içinde yanıt verir." + ways_to_reach_you_message_body: "Ekibin size ulaşması için bir bilgi verin." + email_input_box_message_body: "E-posta ile haberdar olun" reply: - email_subject: "New messages on this conversation" - transcript_subject: "Conversation Transcript" + email_subject: "Bu görüşmedeki yeni mesajlar" + transcript_subject: "Konuşma Metni" + integration_apps: + slack: + name: "Slack" + description: "Slack, tüm iletişiminizi tek bir yerde bir araya getiren bir sohbet aracıdır. Slack'i entegre ederek, hesabınızdaki tüm yeni konuşmalardan Slack'inizin içinde bildirim alabilirsiniz." + webhooks: + name: "Webhooks" + description: "Webhook olayları size hesabınızda gerçekleşen gerçek zamanlı bilgileri getirmenizi sağlar. Bu webhookları kullanarak olaylar ile favori uygulamalarınızı haberleştirebilirsiniz(ör: Slack , \n Github). Yapılandıra basarak webhooklarınızı ayarlayabilirsiniz." diff --git a/config/locales/uk.yml b/config/locales/uk.yml index e35a073747c..09db2e5a293 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -27,6 +27,8 @@ uk: invalid_email: Ви ввели неправильну адресу електронної пошти email_already_exists: "Ви вже зареєстровані з адресою %{email}" failed: Помилка реєстрації + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ uk: reply: email_subject: "New messages on this conversation" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/vi.yml b/config/locales/vi.yml index 58843385709..f1c52133232 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -27,6 +27,8 @@ vi: invalid_email: Bạn đã nhập một email không hợp lệ email_already_exists: "Bạn đã đăng ký một tài khoản với %{email}" failed: Đăng ký thât bại + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ vi: reply: email_subject: "Tin nhắn mới về cuộc trò chuyện này" transcript_subject: "Bản ghi cuộc hội thoại" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/zh_CN.yml b/config/locales/zh_CN.yml index 508458cf4e3..64537ee016c 100644 --- a/config/locales/zh_CN.yml +++ b/config/locales/zh_CN.yml @@ -16,7 +16,7 @@ #'true': 'foo' #To learn more, please read the Rails Internationalization guide #available at https://guides.rubyonrails.org/i18n.html. -zh-CN: +zh_CN: hello: "您好世界" messages: reset_password_success: 哇!密码重置请求成功。请检查您的邮件获取说明。 @@ -27,6 +27,8 @@ zh-CN: invalid_email: 您输入了一个无效的电子邮件 email_already_exists: "您已经注册了 %{email} 的帐户" failed: 注册失败 + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ zh-CN: reply: email_subject: "此对话中的新消息" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/config/locales/zh_TW.yml b/config/locales/zh_TW.yml index 2552d737bcc..46853545d53 100644 --- a/config/locales/zh_TW.yml +++ b/config/locales/zh_TW.yml @@ -16,7 +16,7 @@ #'true': 'foo' #To learn more, please read the Rails Internationalization guide #available at https://guides.rubyonrails.org/i18n.html. -zh-TW: +zh_TW: hello: "你好。" messages: reset_password_success: 密碼重設成功,請確認您的信箱有收到重設信件。 @@ -27,6 +27,8 @@ zh-TW: invalid_email: 您輸入的電子郵件無效。 email_already_exists: "您已經註冊了一個帳號%{email}" failed: 註冊失敗。 + reports: + period: Reporting period %{since} to %{until} conversations: activity: status: @@ -49,3 +51,10 @@ zh-TW: reply: email_subject: "New messages on this conversation" transcript_subject: "Conversation Transcript" + integration_apps: + slack: + name: "Slack" + description: "Slack is a chat tool that brings all your communication together in one place. By integrating Slack, you can get notified of all the new conversations in your account right inside your Slack." + webhooks: + name: "Webhooks" + description: "Webhook events provide you the realtime information about what's happening in your account. You can make use of the webhooks to communicate the events to your favourite apps like Slack or Github. Click on Configure to set up your webhooks." diff --git a/db/seeds.rb b/db/seeds.rb index b56da8239ad..3b139500453 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -12,6 +12,8 @@ user = User.new(name: 'John', email: 'john@acme.inc', password: '123456') user.skip_confirmation! user.save! +SuperAdmin.create!(email: 'john@acme.inc', password: '123456') unless Rails.env.production? + AccountUser.create!( account_id: account.id, user_id: user.id, diff --git a/deployment/chatwoot-web.1.service b/deployment/chatwoot-web.1.service index 56d6a283cb5..9ef63aeb6cc 100644 --- a/deployment/chatwoot-web.1.service +++ b/deployment/chatwoot-web.1.service @@ -16,10 +16,10 @@ KillMode=mixed StandardInput=null SyslogIdentifier=%p -Environment="PATH=/home/chatwoot/.rvm/gems/ruby-2.7.1/bin:/home/chatwoot/.rvm/gems/ruby-2.7.1@global/bin:/home/chatwoot/.rvm/rubies/ruby-2.7.1/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin" +Environment="PATH=/home/chatwoot/.rvm/gems/ruby-2.7.2/bin:/home/chatwoot/.rvm/gems/ruby-2.7.2@global/bin:/home/chatwoot/.rvm/rubies/ruby-2.7.2/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin" Environment="PORT=3000" Environment="RAILS_ENV=production" Environment="NODE_ENV=production" Environment="RAILS_LOG_TO_STDOUT=true" -Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-2.7.1" -Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-2.7.1:/home/chatwoot/.rvm/gems/ruby-2.7.1@global" \ No newline at end of file +Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-2.7.2" +Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-2.7.2:/home/chatwoot/.rvm/gems/ruby-2.7.2@global" diff --git a/deployment/chatwoot-worker.1.service b/deployment/chatwoot-worker.1.service index 8b3f0b1cba8..ac24709cddc 100644 --- a/deployment/chatwoot-worker.1.service +++ b/deployment/chatwoot-worker.1.service @@ -16,10 +16,10 @@ KillMode=mixed StandardInput=null SyslogIdentifier=%p -Environment="PATH=/home/chatwoot/.rvm/gems/ruby-2.7.1/bin:/home/chatwoot/.rvm/gems/ruby-2.7.1@global/bin:/home/chatwoot/.rvm/rubies/ruby-2.7.1/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin" +Environment="PATH=/home/chatwoot/.rvm/gems/ruby-2.7.2/bin:/home/chatwoot/.rvm/gems/ruby-2.7.2@global/bin:/home/chatwoot/.rvm/rubies/ruby-2.7.2/bin:/home/chatwoot/.rvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin:/home/chatwoot/.rvm/bin:/home/chatwoot/.rvm/bin" Environment="PORT=3000" Environment="RAILS_ENV=production" Environment="NODE_ENV=production" Environment="RAILS_LOG_TO_STDOUT=true" -Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-2.7.1" -Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-2.7.1:/home/chatwoot/.rvm/gems/ruby-2.7.1@global" \ No newline at end of file +Environment="GEM_HOME=/home/chatwoot/.rvm/gems/ruby-2.7.2" +Environment="GEM_PATH=/home/chatwoot/.rvm/gems/ruby-2.7.2:/home/chatwoot/.rvm/gems/ruby-2.7.2@global" diff --git a/deployment/setup_18.04.sh b/deployment/setup_18.04.sh index cacd995e928..25af78256cc 100644 --- a/deployment/setup_18.04.sh +++ b/deployment/setup_18.04.sh @@ -43,12 +43,16 @@ RAILS_ENV=production sudo -i -u chatwoot << EOF rvm --version rvm autolibs disable -rvm install "ruby-2.7.1" -rvm use 2.7.1 --default +rvm install "ruby-2.7.2" +rvm use 2.7.2 --default git clone https://github.com/chatwoot/chatwoot.git cd chatwoot -git checkout master +if [[ -z "$1" ]]; then + git checkout master; +else + git checkout $1; +fi bundle yarn diff --git a/deployment/setup_20.04.sh b/deployment/setup_20.04.sh index 6541b6a5ea8..19f32fe12cc 100644 --- a/deployment/setup_20.04.sh +++ b/deployment/setup_20.04.sh @@ -43,12 +43,16 @@ RAILS_ENV=production sudo -i -u chatwoot << EOF rvm --version rvm autolibs disable -rvm install "ruby-2.7.1" -rvm use 2.7.1 --default +rvm install "ruby-2.7.2" +rvm use 2.7.2 --default git clone https://github.com/chatwoot/chatwoot.git cd chatwoot -git checkout master +if [[ -z "$1" ]]; then + git checkout master; +else + git checkout $1; +fi bundle yarn diff --git a/docker/Dockerfile b/docker/Dockerfile index 8093fdb0212..99a82b8b2e6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ # pre-build stage -FROM ruby:2.7.1-alpine AS pre-builder +FROM ruby:2.7.2-alpine AS pre-builder # ARG default to production settings # For development docker-compose file overrides ARGS @@ -51,7 +51,7 @@ RUN if [ "$RAILS_ENV" = "production" ]; then \ fi # final build stage -FROM ruby:2.7.1-alpine +FROM ruby:2.7.2-alpine ARG BUNDLE_WITHOUT="development:test" ENV BUNDLE_WITHOUT ${BUNDLE_WITHOUT} diff --git a/docs/contributing.md b/docs/contributing.md index aa7a00df211..4ac423be80f 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -19,15 +19,15 @@ Thank you for taking an interest in contributing to Chatwoot. Before submitting - It's okay and encouraged to have multiple small commits as you work on the PR - we will squash the commits before merging. -### Getting Started +### Getting Started - Before starting your work, ensure an [issue](https://github.com/chatwoot/chatwoot/issues) exist for it. If not feel free to create one. -- Add a comment on the issue and wait for the issue to be assigned before you start working on it. - - This helps to avoid multiple people working on similar issues. -- If the solution is complex, propose the solution on the issue and wait for one of the core contributors to approve before going into the implementation. +- Add a comment on the issue and wait for the issue to be assigned before you start working on it. + - This helps to avoid multiple people working on similar issues. +- If the solution is complex, propose the solution on the issue and wait for one of the core contributors to approve before going into the implementation. - This helps in shorter turn around times in merging PRs -- For new feature requests, Provide a convincing reason to add this feature. Real-life business use-cases will be super helpful. -- Feel free to join our [discord community](https://discord.gg/cJXdrwS), if you need further discussions with the core team. +- For new feature requests, Provide a convincing reason to add this feature. Real-life business use-cases will be super helpful. +- Feel free to join our [discord community](https://discord.gg/cJXdrwS), if you need further discussions with the core team. ### Developing a new feature: @@ -38,9 +38,7 @@ Thank you for taking an interest in contributing to Chatwoot. Before submitting - If you are resolving a particular issue, add `Bug: Fix xxxx` (#xxxx is the issue) in your PR title. - Provide a detailed description of the bug in the PR. - Add appropriate test coverage if applicable. - -### Translations: -- When you are introducing new text copies, you only need to worry about making changes to english language files. -- We accept language translations / updates for existing translations through [crowdin](https://translate.chatwoot.com/) - - If a language doesn't exist in our crowdin, please feel free to create an [issue](https://github.com/chatwoot/chatwoot/issues) to get it enabled. - + +### Translations + +For language translations, please read the guide [translating Chatwoot to your language](/docs/contributing/translating-chatwoot-to-your-language) diff --git a/docs/contributing/translating-chatwoot-to-your-language.md b/docs/contributing/translating-chatwoot-to-your-language.md new file mode 100644 index 00000000000..4c9f149e373 --- /dev/null +++ b/docs/contributing/translating-chatwoot-to-your-language.md @@ -0,0 +1,34 @@ +--- +path: "/docs/contributing/translating-chatwoot-to-your-language" +title: "Translate Chatwoot to your language" +--- + +Chatwoot uses American English by default. Each and every string available in Chatwoot can be translated to the language of your choice. Chatwoot uses Crowdin to manage the translation process. The updates from Crowdin is also included along with every release. + +### How do I see the strings that needs to be translated? + +In the codebase the string are placed in the following locations. + +- `app/javascript/dashboard/i18n` - The strings related to the agent dashboard +- `app/javascript/widget/i18n` - The strings related to the web widget +- `config/locales` - The strings used in backend messages or API response. + +Alternatively, you can login to the **Crowdin** ([https://translate.chatwoot.com](https://translate.chatwoot.com)) and create an account to view the strings that needs to be translated. + +### How to contribute? + +If you don't find your language on Crowdin, please create an issue on [Github](https://github.com/chatwoot/chatwoot/issues) to add the language. + +#### Translate Strings + +The translation process for Chatwoot web and mobile app is managed at [https://translate.chatwoot.com](https://translate.chatwoot.com) using Crowdin. You will have to create an account at Crowdin before you can select a language and contribute. + +#### Proofreading + +Proofreading helps ensure the accuracy and consistency of translations. Right now, the translations are being accepted without a proof reading step. This would be changed in the future as an when there are more contributors on each language. + +*Note:* If you are the only person contributing to a language, make sure that you inform any of the Chatwoot members to gain access to manage the language. + +#### Releasing a new language + +All the translated strings would be included in the next release. If a language has *60% or more translated strings* in Crowdin, we would enable the language in Chatwoot app during the next release. diff --git a/docs/deployment/production/docker.md b/docs/deployment/production/docker.md index 92249c6c902..57db212dda6 100644 --- a/docs/deployment/production/docker.md +++ b/docs/deployment/production/docker.md @@ -12,8 +12,8 @@ We publish our base images to docker hub. Build your web/worker images from thes ``` FROM chatwoot/chatwoot:latest RUN chmod +x docker/entrypoints/rails.sh -ENTRYPOINT [\"docker/entrypoints/rails.sh\"] -CMD bundle exec bundle exec rails s -b 0.0.0.0 -p 3000" +ENTRYPOINT ["docker/entrypoints/rails.sh"] +CMD bundle exec bundle exec rails s -b 0.0.0.0 -p 3000 ``` ### worker @@ -21,8 +21,8 @@ CMD bundle exec bundle exec rails s -b 0.0.0.0 -p 3000" ``` FROM chatwoot/chatwoot:latest RUN chmod +x docker/entrypoints/rails.sh -ENTRYPOINT [\"docker/entrypoints/rails.sh\"] -CMD bundle exec sidekiq -C config/sidekiq.yml" +ENTRYPOINT ["docker/entrypoints/rails.sh"] +CMD bundle exec sidekiq -C config/sidekiq.yml ``` The app servers will available on port `3000`. Ensure the images are connected to the same database and Redis servers. Provide the configuration for these services via environment variables. diff --git a/docs/deployment/production/linux-vm.md b/docs/deployment/production/linux-vm.md index 0aeb94b2c0a..39e507ab71f 100644 --- a/docs/deployment/production/linux-vm.md +++ b/docs/deployment/production/linux-vm.md @@ -26,7 +26,7 @@ https://raw.githubusercontent.com/chatwoot/chatwoot/develop/deployment/setup_20. ```bash wget -O setup.sh chmod 755 setup.sh -./setup.sh +./setup.sh master ``` 2. Execute the script and it will take care of the initial **Chatwoot** setup. @@ -147,7 +147,21 @@ rake assets:precompile RAILS_ENV=production # Migrate the database schema RAILS_ENV=production bundle exec rake db:migrate +# Copy the updated targets +cp /home/chatwoot/chatwoot/deployment/chatwoot-web.1.service /etc/systemd/system/chatwoot-web.1.service +cp /home/chatwoot/chatwoot/deployment/chatwoot-worker.1.service /etc/systemd/system/chatwoot-worker.1.service +cp /home/chatwoot/chatwoot/deployment/chatwoot.target /etc/systemd/system/chatwoot.target + # Restart the chatwoot server systemctl restart chatwoot.target +``` + +#### If precompile fails + +If the asset precompilation step fails with `ActionView::Template::Error (Webpacker can't find application.css in /home/chatwoot/chatwoot/public/packs/manifest.json)` or if you face issues while restarting the server, try the following command and restart the server. ``` +RAILS_ENV=production rake assets:clean assets:clobber assets:precompile +``` + +This command would clear the existing compiled assets and would recompile all the assets. Read more about it [here](https://edgeguides.rubyonrails.org/command_line.html#bin-rails-assets) diff --git a/docs/development/environment-setup/mac-os.md b/docs/development/environment-setup/mac-os.md index d856b5b155d..8f11469af74 100644 --- a/docs/development/environment-setup/mac-os.md +++ b/docs/development/environment-setup/mac-os.md @@ -28,28 +28,26 @@ brew install git ### Install RVM -You need software-properties-common installed in order to add PPA repositories. - ```bash curl -L https://get.rvm.io | bash -s stable -source ~/.rvm/scripts/rvm ``` ### Install Ruby -Chatwoot APIs are built on Ruby on Rails, you need install ruby 2.7.1 +Chatwoot APIs are built on Ruby on Rails, you need install ruby 2.7.2 If you are using `rvm` : ```bash -rvm install ruby-2.7.1 -rvm use 2.7.1 +rvm install ruby-2.7.2 +rvm use 2.7.2 +source ~/.rvm/scripts/rvm ``` If you are using `rbenv` to manage ruby versions do : ```bash -rbenv install 2.7.1 +rbenv install 2.7.2 ``` `rbenv` identifies the ruby version from `.ruby-version` file on the root of the project and loads it automatically. @@ -72,7 +70,13 @@ brew install yarn ### Install postgres -The database used in Chatwoot is PostgreSQL. Use the following commands to install postgres. +The database used in Chatwoot is PostgreSQL. + +1) Install PostgresApp (https://postgresapp.com). This is easiest way to get started with PostgreSQL on mac. + +or + +2) Use the following commands to install postgres. ```bash brew install postgresql @@ -92,10 +96,10 @@ Chatwoot uses Redis server in agent assignments and reporting. To install `redis brew install redis ``` -Enable Redis to start on system boot. +Start the redis service. ```bash -launchctl unload ~/Library/LaunchAgents/homebrew.mxcl.redis.plist +brew services start redis ``` ### Install imagemagick diff --git a/docs/development/environment-setup/ubuntu.md b/docs/development/environment-setup/ubuntu.md index 7e6570f47a4..a97c6a46aa2 100644 --- a/docs/development/environment-setup/ubuntu.md +++ b/docs/development/environment-setup/ubuntu.md @@ -33,16 +33,16 @@ Enable `Run command as a login shell` in terminal `Preferences`. Restart your co ### Install Ruby -Chatwoot APIs are built on Ruby on Rails. You need to install ruby 2.7.1: +Chatwoot APIs are built on Ruby on Rails. You need to install ruby 2.7.2: ```bash -rvm install ruby-2.7.1 +rvm install ruby-2.7.2 ``` -Use ruby 2.7.1 as default: +Use ruby 2.7.2 as default: ```bash -rvm use 2.7.1 --default +rvm use 2.7.2 --default ``` ### Install Node.js diff --git a/docs/development/environment-setup/windows.md b/docs/development/environment-setup/windows.md index 25e7c012195..db308ed4a58 100644 --- a/docs/development/environment-setup/windows.md +++ b/docs/development/environment-setup/windows.md @@ -26,15 +26,15 @@ sudo apt-get update sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev software-properties-common libffi-dev ``` -Install RVM & ruby version 2.7.1 +Install RVM & ruby version 2.7.2 ```bash sudo apt-get install libgdbm-dev libncurses5-dev automake libtool bison libffi-dev gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB curl -sSL https://get.rvm.io | bash -s stable source ~/.rvm/scripts/rvm -rvm install 2.7.1 -rvm use 2.7.1 --default +rvm install 2.7.2 +rvm use 2.7.2 --default ruby -v ``` diff --git a/docs/development/project-setup/environment-variables.md b/docs/development/project-setup/environment-variables.md index d9ae9542c64..faa04f9e480 100644 --- a/docs/development/project-setup/environment-variables.md +++ b/docs/development/project-setup/environment-variables.md @@ -93,6 +93,12 @@ Provide the following value as frontend url FRONTEND_URL='http://localhost:3000' ``` +### Configure default language + +```bash +DEFAULT_LOCALE='en' +``` + ### Configure storage Chatwoot uses [active storage](https://edgeguides.rubyonrails.org/active_storage_overview.html) for storing attachments. The default storage option is the local storage on your server. diff --git a/docs/development/project-setup/errors.md b/docs/development/project-setup/errors.md index d4e5b9e0271..7453745f6a4 100644 --- a/docs/development/project-setup/errors.md +++ b/docs/development/project-setup/errors.md @@ -5,8 +5,35 @@ title: "Common Errors" ### Errors you might encounter while setting up the project + +#### Redis connection error + ```bash ArgumentError: invalid uri scheme ``` This is an error thrown from redis connector. You might not have setup the redis environment variables properly. Please refer to dependencies section to install redis-server and [Configure Redis URL](https://www.chatwoot.com/docs/environment-variables) in the environment-variables section. + + +#### pg gem Installation error + +If you see the following error while bundle installation, provide the Postgres path as pg_config. + +``` +Gem::Ext::BuildError: ERROR: Failed to build gem native extension. + +An error occurred while installing pg (1.2.3), and Bundler cannot +continue. +Make sure that `gem install pg -v '1.2.3' --source 'https://rubygems.org/'` +succeeds before bundling. + +checking for pg_config... no +No pg_config... trying anyway. If building fails, please try again with + --with-pg-config=/path/to/pg_config +``` + +To fix this, try executing + +``` +gem install pg -v '1.2.3' --source 'https://rubygems.org/' -- --with-pg-config=path-to-postgres-installation/12/bin/pg_config +``` diff --git a/docs/development/project-setup/quick-setup.md b/docs/development/project-setup/quick-setup.md index 2c802bead8e..8ad42acafc1 100644 --- a/docs/development/project-setup/quick-setup.md +++ b/docs/development/project-setup/quick-setup.md @@ -11,6 +11,8 @@ Use the following command to install ruby dependencies. bundle ``` +If you face issue with pg gem, please refer to [Common Errors](/docs/common-errors#pg-gem-installation-error) + ### Install JavaScript dependencies ```bash @@ -19,6 +21,12 @@ yarn This would install all required dependencies for Chatwoot application. +### Setup environment variables. + +``` +cp .env.example .env +``` + Please refer to [environment-variables](./environment-variables) to read on setting environment variables. ### Setup rails server @@ -81,7 +89,7 @@ docker-compose run -rm server bundle exec rake db:reset This command essentially runs postgres and redis containers and then run the rake command inside the chatwoot server container. -### Running Cypress Tests +### Running Cypress Tests Refer the docs to learn how to write cypress specs https://github.com/shakacode/cypress-on-rails @@ -89,7 +97,7 @@ https://docs.cypress.io/guides/overview/why-cypress.html ``` # in terminal tab1 -overmind start -f Procfile.test +overmind start -f Procfile.test # in terminal tab2 yarn cypress open --project ./test ``` diff --git a/docs/self-hosted/enable-ip-logging.md b/docs/self-hosted/enable-ip-logging.md new file mode 100644 index 00000000000..aba6edde866 --- /dev/null +++ b/docs/self-hosted/enable-ip-logging.md @@ -0,0 +1,32 @@ +--- +path: "/docs/self-hosted/enable-ip-logging" +title: "Chatwoot Production deployment guide" +--- + +Chatwoot allows you to identify the location of the user by geocoding the IP address. For IP Address geocoding, we support MaxmindDB services. This lookup provides methods for geocoding IP addresses without making a call to a remote API everytime. To setup your self-hosted instance with the geocoding, follow the steps below. + +**Step 1:** Create an account at [MaxmindDB](https://www.maxmind.com) and create an API key. + +**Step 2:** Add the following environment variables. + +```bash +IP_LOOKUP_SERVICE=geoip2 +IP_LOOKUP_API_KEY=your-api-key +``` + +With this step, Chatwoot would automatically download the [MaxmindDB downloadable databases](https://dev.maxmind.com/geoip/geoip2/downloadable/) and cache it locally. + +**Step 3:** Enable IP Lookup on your account. + +Login to Rails console + +``` +RAILS_ENV=production bundle exec rails console +``` + +```rb +account_id = 1 // Please fill your account id instead of 1 +account = Account.find(account_id) +account.enable_features('ip_lookup') +account.save! +``` diff --git a/lib/integrations/slack/channel_builder.rb b/lib/integrations/slack/channel_builder.rb index aa2aa992afc..2092f359418 100644 --- a/lib/integrations/slack/channel_builder.rb +++ b/lib/integrations/slack/channel_builder.rb @@ -21,8 +21,8 @@ class Integrations::Slack::ChannelBuilder end def find_or_create_channel - exisiting_channel = slack_client.conversations_list.channels.find { |channel| channel['name'] == params[:channel] } - @channel = exisiting_channel || slack_client.conversations_create(name: params[:channel])['channel'] + existing_channel = slack_client.conversations_list.channels.find { |channel| channel['name'] == params[:channel] } + @channel = existing_channel || slack_client.conversations_create(name: params[:channel])['channel'] end def update_reference_id diff --git a/lib/integrations/slack/send_on_slack_service.rb b/lib/integrations/slack/send_on_slack_service.rb index 89c6d0b3267..87040c04aa2 100644 --- a/lib/integrations/slack/send_on_slack_service.rb +++ b/lib/integrations/slack/send_on_slack_service.rb @@ -24,7 +24,11 @@ class Integrations::Slack::SendOnSlackService < Base::SendOnChannelService def perform_reply send_message + + return unless @slack_message + update_reference_id + update_external_source_id_slack end def message_content @@ -68,6 +72,12 @@ class Integrations::Slack::SendOnSlackService < Base::SendOnChannelService conversation.update!(identifier: @slack_message['ts']) end + def update_external_source_id_slack + return unless @slack_message['message'] + + message.update!(external_source_id_slack: "cw-origin-#{@slack_message['message']['ts']}") + end + def slack_client @slack_client ||= Slack::Web::Client.new(token: hook.access_token) end diff --git a/lib/redis/redis_keys.rb b/lib/redis/redis_keys.rb index 78a10e27762..454a3715a92 100644 --- a/lib/redis/redis_keys.rb +++ b/lib/redis/redis_keys.rb @@ -1,7 +1,17 @@ module Redis::RedisKeys + ## Inbox Keys + # Array storing the ordered ids for agent round robin assignment ROUND_ROBIN_AGENTS = 'ROUND_ROBIN_AGENTS:%d'.freeze + ## Conversation keys + # Detect whether to send an email reply to the conversation CONVERSATION_MAILER_KEY = 'CONVERSATION::%d'.freeze + # Whether a conversation is muted ? + CONVERSATION_MUTE_KEY = 'CONVERSATION::%d::MUTED'.freeze + + ## User Keys + # SSO Auth Tokens + USER_SSO_AUTH_TOKEN = 'USER_SSO_AUTH_TOKEN::%d::%s'.freeze ## Online Status Keys # hash containing user_id key and status as value @@ -12,6 +22,7 @@ module Redis::RedisKeys ONLINE_PRESENCE_USERS = 'ONLINE_PRESENCE::%d::USERS'.freeze ## Authorization Status Keys + # Used to track token expiry and such issues for facebook slack integrations etc AUTHORIZATION_ERROR_COUNT = 'AUTHORIZATION_ERROR_COUNT:%s:%d'.freeze REAUTHORIZATION_REQUIRED = 'REAUTHORIZATION_REQUIRED:%s:%d'.freeze end diff --git a/package.json b/package.json index caae6fd588c..85a9b5bf83b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@chatwoot/chatwoot", - "version": "1.10.0", + "version": "1.11.0", "license": "MIT", "scripts": { "eslint": "eslint app/javascript --fix", @@ -21,10 +21,11 @@ "chart.js": "~2.5.0", "copy-text-to-clipboard": "^2.1.1", "core-js": "3", + "country-code-emoji": "^1.0.0", "date-fns": "^2.16.1", "dotenv": "^8.0.0", "foundation-sites": "~6.5.3", - "highlight.js": "^9.15.10", + "highlight.js": "~10.4.1", "ionicons": "~2.0.1", "js-cookie": "^2.2.1", "lodash.groupby": "^4.6.0", @@ -40,8 +41,7 @@ "vue-chartjs": "^3.4.2", "vue-clickaway": "~2.1.0", "vue-color": "^2.7.1", - "vue-highlight.js": "^3.1.0", - "vue-i18n": "~5.0.3", + "vue-i18n": "^8.22.1", "vue-loader": "^15.7.0", "vue-multiselect": "~2.1.6", "vue-router": "~2.2.0", diff --git a/spec/controllers/api/v1/accounts/contacts_controller_spec.rb b/spec/controllers/api/v1/accounts/contacts_controller_spec.rb index 486a844b45c..413b9cd94d6 100644 --- a/spec/controllers/api/v1/accounts/contacts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/contacts_controller_spec.rb @@ -90,7 +90,7 @@ RSpec.describe 'Contacts API', type: :request do context 'when it is an authenticated user' do let(:admin) { create(:user, account: account, role: :administrator) } let!(:contact1) { create(:contact, account: account) } - let!(:contact2) { create(:contact, account: account, email: 'test@test.com') } + let!(:contact2) { create(:contact, name: 'testcontact', account: account, email: 'test@test.com') } it 'returns all contacts with contact inboxes' do get "/api/v1/accounts/#{account.id}/contacts/search", @@ -102,6 +102,28 @@ RSpec.describe 'Contacts API', type: :request do expect(response.body).to include(contact2.email) expect(response.body).not_to include(contact1.email) end + + it 'matches the contact ignoring the case in email' do + get "/api/v1/accounts/#{account.id}/contacts/search", + params: { q: 'Test@Test.com' }, + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + expect(response.body).to include(contact2.email) + expect(response.body).not_to include(contact1.email) + end + + it 'matches the contact ignoring the case in name' do + get "/api/v1/accounts/#{account.id}/contacts/search", + params: { q: 'TestContact' }, + headers: admin.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:success) + expect(response.body).to include(contact2.email) + expect(response.body).not_to include(contact1.email) + end end end diff --git a/spec/controllers/devise/session_controller_spec.rb b/spec/controllers/devise/session_controller_spec.rb index 0beb3ebfae5..ef51f5e4be0 100644 --- a/spec/controllers/devise/session_controller_spec.rb +++ b/spec/controllers/devise/session_controller_spec.rb @@ -30,5 +30,36 @@ RSpec.describe 'Session', type: :request do expect(response.body).to include(user.email) end end + + context 'when it is invalid sso auth token' do + let!(:user) { create(:user, password: 'test1234', account: account) } + + it 'returns unauthorized' do + params = { email: user.email, sso_auth_token: SecureRandom.hex(32) } + + post new_user_session_url, + params: params, + as: :json + expect(response).to have_http_status(:unauthorized) + expect(response.body).to include('Invalid login credentials') + end + end + + context 'when with valid sso auth token' do + let!(:user) { create(:user, password: 'test1234', account: account) } + + it 'returns successful auth response' do + params = { email: user.email, sso_auth_token: user.generate_sso_auth_token } + + post new_user_session_url, params: params, as: :json + + expect(response).to have_http_status(:success) + expect(response.body).to include(user.email) + + # token won't work on a subsequent request + post new_user_session_url, params: params, as: :json + expect(response).to have_http_status(:unauthorized) + end + end end end diff --git a/spec/factories/contacts.rb b/spec/factories/contacts.rb index ec5046fbf69..eaecd67e978 100644 --- a/spec/factories/contacts.rb +++ b/spec/factories/contacts.rb @@ -2,8 +2,8 @@ FactoryBot.define do factory :contact do - sequence(:name) { |n| "Widget #{n}" } - sequence(:email) { |n| "widget-#{n}@example.com" } + sequence(:name) { |n| "Contact #{n}" } + sequence(:email) { |n| "contact-#{n}@example.com" } phone_number { '+123456789011' } avatar { fixture_file_upload(Rails.root.join('spec/assets/avatar.png'), 'image/png') } account diff --git a/spec/lib/integrations/slack/send_on_slack_service_spec.rb b/spec/lib/integrations/slack/send_on_slack_service_spec.rb index dc36de65f51..234a2180ba3 100644 --- a/spec/lib/integrations/slack/send_on_slack_service_spec.rb +++ b/spec/lib/integrations/slack/send_on_slack_service_spec.rb @@ -1,50 +1,78 @@ require 'rails_helper' describe Integrations::Slack::SendOnSlackService do - let(:account) { create(:account) } - let!(:inbox) { create(:inbox, account: account) } let!(:contact) { create(:contact) } - + let!(:conversation) { create(:conversation, contact: contact, identifier: nil) } + let(:account) { conversation.account } let!(:hook) { create(:integrations_hook, account: account) } - let!(:conversation) { create(:conversation, account: account, inbox: inbox, contact: contact) } - let!(:message) { create(:message, account: account, inbox: inbox, conversation: conversation) } + let!(:message) do + create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation) + end + let(:slack_message) { double } + let(:slack_message_content) { double } + let(:slack_client) { double } + let(:builder) { described_class.new(message: message, hook: hook) } + + before do + allow(builder).to receive(:slack_client).and_return(slack_client) + allow(slack_message).to receive(:[]).with('ts').and_return('12345.6789') + allow(slack_message).to receive(:[]).with('message').and_return(slack_message_content) + allow(slack_message_content).to receive(:[]).with('ts').and_return('6789.12345') + end describe '#perform' do - it 'sent message to slack' do - builder = described_class.new(message: message, hook: hook) - stub_request(:post, 'https://slack.com/api/chat.postMessage') - .to_return(status: 200, body: '', headers: {}) - slack_client = double - expect(builder).to receive(:slack_client).and_return(slack_client) + context 'without identifier' do + it 'updates slack thread id in conversation' do + inbox = conversation.inbox - expect(slack_client).to receive(:chat_postMessage).with( - channel: hook.reference_id, - text: message.content, - username: "Contact: #{message.sender.name}", - thread_ts: conversation.identifier, - icon_url: anything - ) + expect(slack_client).to receive(:chat_postMessage).with( + channel: hook.reference_id, + text: "*Inbox: #{inbox.name} [#{inbox.inbox_type}]* \n\n #{message.content}", + username: "Contact: #{message.sender.name}", + thread_ts: nil, + icon_url: anything + ).and_return(slack_message) - builder.perform + builder.perform + + expect(conversation.reload.identifier).to eq '12345.6789' + end end - it 'disables hook on Slack AccountInactive error' do - builder = described_class.new(message: message, hook: hook) - slack_client = double - expect(builder).to receive(:slack_client).and_return(slack_client) - expect(slack_client).to receive(:chat_postMessage).with( - channel: hook.reference_id, - text: message.content, - username: "Contact: #{message.sender.name}", - thread_ts: conversation.identifier, - icon_url: anything - ).and_raise(Slack::Web::Api::Errors::AccountInactive.new('Account disconnected')) + context 'with identifier' do + before do + conversation.update!(identifier: 'random_slack_thread_ts') + end - allow(hook).to receive(:authorization_error!) + it 'sent message to slack' do + expect(slack_client).to receive(:chat_postMessage).with( + channel: hook.reference_id, + text: message.content, + username: "Contact: #{message.sender.name}", + thread_ts: conversation.identifier, + icon_url: anything + ).and_return(slack_message) - builder.perform - expect(hook).to be_disabled - expect(hook).to have_received(:authorization_error!) + builder.perform + + expect(message.external_source_id_slack).to eq 'cw-origin-6789.12345' + end + + it 'disables hook on Slack AccountInactive error' do + expect(slack_client).to receive(:chat_postMessage).with( + channel: hook.reference_id, + text: message.content, + username: "Contact: #{message.sender.name}", + thread_ts: conversation.identifier, + icon_url: anything + ).and_raise(Slack::Web::Api::Errors::AccountInactive.new('Account disconnected')) + + allow(hook).to receive(:authorization_error!) + + builder.perform + expect(hook).to be_disabled + expect(hook).to have_received(:authorization_error!) + end end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 0321fb93a88..225eedeb72a 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -26,4 +26,25 @@ RSpec.describe User do it { expect(user.pubsub_token).not_to eq(nil) } it { expect(user.saved_changes.keys).not_to eq('pubsub_token') } end + + context 'sso_auth_token' do + it 'can generate multiple sso tokens which can be validated' do + sso_auth_token1 = user.generate_sso_auth_token + sso_auth_token2 = user.generate_sso_auth_token + expect(sso_auth_token1).present? + expect(sso_auth_token2).present? + expect(user.valid_sso_auth_token?(sso_auth_token1)).to eq true + expect(user.valid_sso_auth_token?(sso_auth_token2)).to eq true + end + + it 'wont validate an invalid token' do + expect(user.valid_sso_auth_token?(SecureRandom.hex(32))).to eq false + end + + it 'wont validate an invalidated token' do + sso_auth_token = user.generate_sso_auth_token + user.invalidate_sso_auth_token(sso_auth_token) + expect(user.valid_sso_auth_token?(sso_auth_token)).to eq false + end + end end diff --git a/yarn.lock b/yarn.lock index 6a816b2ed61..189b91323c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3213,6 +3213,11 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" +country-code-emoji@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/country-code-emoji/-/country-code-emoji-1.0.0.tgz#7c77791839c9e9921beec08ef080caa7cfb8b40c" + integrity sha512-fBM5A49oZkOxOVb0bx7q7Hanlfh8e3z/r6/ZnFhbL57JXGIgWPC2HYrjXEyiGML7OFftDV/WfAlJdDkoAbj1Rg== + create-ecdh@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" @@ -3747,11 +3752,6 @@ detect-file@^1.0.0: resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= -detect-indent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" - integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= - detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -5228,10 +5228,10 @@ hex-color-regex@^1.1.0: resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ== -highlight.js@^9.15.10: - version "9.18.1" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.1.tgz#ed21aa001fe6252bb10a3d76d47573c6539fe13c" - integrity sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg== +highlight.js@~10.4.1: + version "10.4.1" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.4.1.tgz#d48fbcf4a9971c4361b3f95f302747afe19dbad0" + integrity sha512-yR5lWvNz7c85OhVAEAeFhVCc/GV4C30Fjzc/rCP0aCWzc1UUOPUk55dK/qdwTZHBvMZo+eZ2jpk62ndX/xMFlg== hmac-drbg@^1.0.0: version "1.0.1" @@ -5545,9 +5545,9 @@ inherits@2.0.3: integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= ini@^1.3.4, ini@^1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + version "1.3.7" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" + integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== inquirer@^7.0.0: version "7.1.0" @@ -9238,14 +9238,6 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" -redent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa" - integrity sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo= - dependencies: - indent-string "^3.0.0" - strip-indent "^2.0.0" - reduce-css-calc@^2.1.6: version "2.1.7" resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz#1ace2e02c286d78abcd01fd92bfe8097ab0602c2" @@ -10310,11 +10302,6 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" -strip-indent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" - integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g= - strip-json-comments@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -10718,7 +10705,7 @@ tsconfig@^7.0.0: strip-bom "^3.0.0" strip-json-comments "^2.0.0" -tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.9.0: version "1.11.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== @@ -11062,24 +11049,15 @@ vue-eslint-parser@^7.0.0: esquery "^1.0.1" lodash "^4.17.15" -vue-highlight.js@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/vue-highlight.js/-/vue-highlight.js-3.1.0.tgz#87b60b4931fd310b318f2b2c9116fe71b69dd053" - integrity sha512-i55SERtdV0CYQppGo29iT6NOq+oOenOKVwkLWZRt7bSynbsQoj/e8GJy/5xL1s5OOYObC/CxA39bRadVyPQt1A== - dependencies: - detect-indent "^5.0.0" - redent "^2.0.0" - tslib "^1.9.3" - vue-hot-reload-api@^2.3.0: version "2.3.4" resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2" integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog== -vue-i18n@~5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-5.0.3.tgz#b6d96cc832604237e6139de471e0d4c820aedbed" - integrity sha1-ttlsyDJgQjfmE53kceDUyCCu2+0= +vue-i18n@^8.22.1: + version "8.22.1" + resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-8.22.1.tgz#b9dd098a17e1f5adb91bdf9611f0385310da7cb1" + integrity sha512-JNgiEJ5a8YPfk5y2lKyfOAGLmkpAVfhaUi+T4wGpSppRYZ3XSyawSDDketY5KV2CsAiBLAGEIO6jO+0l2hQubg== vue-jest@^3.0.5: version "3.0.5"