FEATURE: Site setting for typographic quotation marks

FEATURE: Site setting for typographic quotation marks

Adds locale defaults for German and French

diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6
index 3c91bf0..447b504 100644
--- a/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6
+++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown-it.js.es6
@@ -287,7 +287,8 @@ export function setup(opts, siteSettings, state) {
     breaks: opts.discourse.features.newline,
     xhtmlOut: false,
     linkify: siteSettings.enable_markdown_linkify,
-    typographer: siteSettings.enable_markdown_typographer
+    typographer: siteSettings.enable_markdown_typographer,
+    quotes: siteSettings.markdown_typographer_quotation_marks.split("|")
   });
 
   opts.engine.linkify.tlds(
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 6646bcb..0a7a0af 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -1423,6 +1423,7 @@ en:
     enable_markdown_typographer: "Use typography rules to improve readability of text: replace straight quotes ' with curly quotes ’, (c) (tm) with symbols, -- with emdash –, etc"
     enable_markdown_linkify: "Automatically treat text that looks like a link as a link: www.example.com and https://example.com will be automatically linked"
     markdown_linkify_tlds: "List of top level domains that get automatically treated as links"
+    markdown_typographer_quotation_marks: "List of double and single quotes replacement pairs"
     post_undo_action_window_mins: "Number of minutes users are allowed to undo recent actions on a post (like, flag, etc)."
     must_approve_users: "Staff must approve all new user accounts before they are allowed to access the site."
     pending_users_reminder_delay: "Notify moderators if new users have been waiting for approval for longer than this many hours. Set to -1 to disable notifications."
@@ -2092,6 +2093,7 @@ en:
         regex_invalid: "The regular expression is invalid: %{error}"
         leading_trailing_slash: "The regular expression must not start and end with a slash."
       unicode_usernames_avatars: "The internal system avatars do not support Unicode usernames."
+      list_value_count: "The list must contain exactly %{count} values."
 
     placeholder:
       sso_provider_secrets:
diff --git a/config/site_settings.yml b/config/site_settings.yml
index 1a9fbb9..6e17b3c 100644
--- a/config/site_settings.yml
+++ b/config/site_settings.yml
@@ -708,6 +708,15 @@ posting:
     type: list
     default: "com|net|org|io|co|tv|ru|cn|us|uk|me|de|fr|fi|gov"
     list_type: compact
+  markdown_typographer_quotation_marks:
+    client: true
+    type: list
+    list_type: compact
+    validator: "MarkdownTypographerQuotationMarksValidator"
+    default: "“|”|‘|’"
+    locale_default:
+      de: "„|“|‚|‘"
+      fr: "«\xA0|\xA0»|‹\xA0|\xA0›"
   enable_rich_text_paste:
     client: true
     default: true
diff --git a/lib/validators/markdown_typographer_quotation_marks_validator.rb b/lib/validators/markdown_typographer_quotation_marks_validator.rb
new file mode 100644
index 0000000..3026210
--- /dev/null
+++ b/lib/validators/markdown_typographer_quotation_marks_validator.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class MarkdownTypographerQuotationMarksValidator
+  QUOTE_COUNT = 4
+
+  def initialize(opts = {})
+    @opts = opts
+  end
+
+  def valid_value?(value)
+    value.present? && value.split("|").size == QUOTE_COUNT
+  end
+
+  def error_message
+    I18n.t("site_settings.errors.list_value_count", count: QUOTE_COUNT)
+  end
+end
diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb
index 74c54bd..045ec8b 100644
--- a/spec/components/pretty_text_spec.rb
+++ b/spec/components/pretty_text_spec.rb
@@ -1062,6 +1062,14 @@ HTML
     expect(PrettyText.cook('(tm)')).to eq('<p>(tm)</p>')
   end
 
+  it 'uses quotation marks from site settings' do
+    SiteSetting.enable_markdown_typographer = true
+    expect(PrettyText.cook(%q|"Do you know," he said, "what 'Discourse' is?"|)).to eq(%q|<p>“Do you know,” he said, “what ‘Discourse’ is?”</p>|)
+
+    SiteSetting.markdown_typographer_quotation_marks = "„|“|‚|‘"
+    expect(PrettyText.cook(%q|"Weißt du", sagte er, "was 'Discourse' ist?"|)).to eq(%q|<p>„Weißt du“, sagte er, „was ‚Discourse‘ ist?“</p>|)
+  end
+
   it 'handles onebox correctly' do
     expect(PrettyText.cook("http://a.com\nhttp://b.com").split("onebox").length).to eq(3)
     expect(PrettyText.cook("http://a.com\n\nhttp://b.com").split("onebox").length).to eq(3)

GitHub sha: 9a11a8b3

1 Like

This commit has been mentioned on Discourse Meta. There might be relevant details there:

Follow-up for 9a11a8b3 to fix qunit tests

Awesome, thank you, Gerhard :blush:.