Note: After saving, changes may not occur immediately. Click here to learn how to bypass your browser's cache.
  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (Cmd-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (Cmd-Shift-R on a Mac)
  • Internet Explorer: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Clear the cache in Tools → Preferences

For details and instructions about other browsers, see Wikipedia:Bypass your cache.

/**
 * This script fixes punctuation (mainly quotation marks, but also dashes).
 * It adds a button to the 'format' section of the editing toolbar.
 * The list of replacements is as used in the Gitenberg project.
 *
 * Based on work from: https://github.com/gitenberg-dev/punctuation-cleanup
 *
 * Recorded on Toolhub at https://toolhub.wikimedia.org/tools/curlyquotes
 */

var punctuationReplacements = [
	{ searchFor: /(That|that|this) 'ere/g, replaceWith: "$1 ’ere"},   //  elided consonant: (h)ere (Youth)
	{ searchFor: /black'earted/g, replaceWith: "black’earted"},   //  black (he)arted (Conrad 'Narcissus')
	{ searchFor: /fo'c'sle/g, replaceWith: "fo’c’sle"},   //  fo'c'sle (forecastle) (Conrad 'Narcissus')
	{ searchFor: /p'r'aps/g, replaceWith: "p’r’aps"},   //  p(e)r(h)aps (Conrad 'Narcissus')
	{ searchFor: /(A|a)'n't/g, replaceWith: "$1’n’t"},   //  ain't (Conrad 'Narcissus')
	{ searchFor: /'(art|ard|ang)/g, replaceWith: "’$1"},   //  (he)art, (h)ard, (h)ang (Conrad 'Narcissus')
	{ searchFor: / '(As|as)\b/g, replaceWith: "’$1"},   //  (h)as (Conrad 'Narcissus')
	{ searchFor: /"'(Cos)/g, replaceWith: "“’$1"},   //  (be)cause (Conrad 'Narcissus')
	{ searchFor: /'(Cos|cos)/g, replaceWith: "’$1"},   //  (be)cause (Conrad 'Narcissus')
	{ searchFor: /(\s)'(ome|old|ell|owling)/g, replaceWith: "$1’$2"},   //  (h)omeward, (h)old, (h)ell, (h)owling (Conrad 'Narcissus')
	//{ searchFor: / 'owling/g, replaceWith: " ’owling"},   //  (h)owling (Conrad 'Narcissus')
	//{ searchFor: /(^|\s)'Ow/g, replaceWith: "$1’Ow"},   //  (H)ow (Conrad 'Narcissus')
	{ searchFor: /(--|—)s'elp/g, replaceWith: "$1s’elp"},   //  so help me (Conrad 'Narcissus')
	{ searchFor: /(\s)S'elp/g, replaceWith: "$1S’elp"},   //  so help me (Conrad 'Narcissus')
	{ searchFor: /ainch'ee/g, replaceWith: "ainch’ee"},   //  ain't ye (Conrad 'Narcissus')
	{ searchFor: / som'thin(g|k)/g, replaceWith: " som’thin$1"},   //  something (Conrad 'Narcissus')
	{ searchFor: /gran'mother/g, replaceWith: "gran’mother"},   //  gran'mother (Conrad 'Narcissus')
	{ searchFor: /(G|g)i'e/g, replaceWith: "$1i’e"},   //  gi(v)e me (Conrad 'Narcissus')
	{ searchFor: / '(ed|ead|ear|ad|un)\b/g, replaceWith: " ’$1"},   //  (h)e(a)d, (h)ad, good 'un (Conrad 'Narcissus')
	{ searchFor: /^'(ead|ee)\b/gm, replaceWith: "’$1"},   //  (h)ead at line beginning (Conrad 'Narcissus')
	{ searchFor: /(^|\s)'im\b/g, replaceWith: "$1’im"},   //  Catch 'im, Give 'im, preceded by space or (Heart of Darkness)
	//{ searchFor: /([C|c]atch|Give|Eat|break|Let|made|do for) 'im/g, replaceWith: "$1 ’im"},   //  Catch 'im, Give 'im (Heart of Darkness)
	{ searchFor: /(Strook)'im/g, replaceWith: "$1’im"},   //  (h)im (Conrad 'Narcissus')
	{ searchFor: /"(')(Ear|It) 'im/g, replaceWith: '“’$2 ’im'},   //  (H)ear (h)im, (H)it (h)im beginning with quote (Conrad 'Narcissus')
	{ searchFor: /"(')(Ere)'s/g, replaceWith: '“’$2’s'},   //  (H)ere's beginning with quote (Conrad 'Narcissus')
	{ searchFor: /(\s)(')(It|it) 'im/g, replaceWith: '$1’$3 ’im'},   //  ((H|h)it (h)im not at beginning of quote (Conrad 'Narcissus')
	{ searchFor: /^'im\./gm, replaceWith: "’im."},   //  catch 'im (Heart of Darkness) [Be specific so as not to change something unwittingly]
	{ searchFor: /'d\b/g, replaceWith: "’d"},    // I'd
	{ searchFor: /'ll\b/g, replaceWith: "’ll"},    //  you'll
	{ searchFor: /'m\b/g, replaceWith: "’m"},    //  I'm
	{ searchFor: /'re\b/g, replaceWith: "’re"},    //  you're
	{ searchFor: /'(s|S)\b/g, replaceWith: "’$1"},    //  it's, GRIMM'S (Youth)
	{ searchFor: /s'(\s)/g, replaceWith: "s’$1"},    //  plural possessive
	{ searchFor: /s'(long)/g, replaceWith: "s’$1"},    //  s'long (Conrad 'Narcissus')
	{ searchFor: /'st\b/g, replaceWith: "’st"},    //  look'st (Rime of the Ancient Mariner)
	{ searchFor: /'t\b/g, replaceWith: "’t"},   //  don't
	{ searchFor: /'ve\b/g, replaceWith: "’ve"},   //  I've
	{ searchFor: /(\s)'(\d\ds)/g, replaceWith: "$1’$2"},   //  ’90s
	{ searchFor: /(\s)'(\d\d)/g, replaceWith: "$1’$2"},   //  in 1965 and ’66 (Hacker Crackdown)
	{ searchFor: /O'([A-Z])/g, replaceWith: "O’$1"},    //  O'Reilly
	{ searchFor: /o'clock/g, replaceWith: "o’clock"},    //  o'clock
	{ searchFor: /(o|e)'e/g, replaceWith: "$1’e"},    //  o'er, ne'er (Rime)
	{ searchFor: /(sou)'west/g, replaceWith: "$1’west"},    //  sou'west (Youth)
	{ searchFor: /'(em|gan|baccy|ave|ee )\b/g, replaceWith: "’$1"},    //  'em, 'gan  (Rime), 'baccy, 'ave, 'ee (Conrad 'Narcissus')
	{ searchFor: /'aven'tchee/g, replaceWith: "’aven’tchee"},    //   'aven't (Conrad 'Narcissus')
	{ searchFor: /'ave/g, replaceWith: "’ave"},    //   'ave (Conrad 'Narcissus')
	//{ searchFor: /",/g, replaceWith: ',”'},    // comma outside quote mark
	//{ searchFor: /"\./g, replaceWith: '.”'},    // period outside quote mark (transpose only)
	{ searchFor: /"\b/g, replaceWith: '“'},    //  open quote (eg, precedes a 'word boundary')
	{ searchFor: /(\?)"' /g, replaceWith: '$1”’ '},    //  close double quote preceding close single quote at sentence end
	{ searchFor: /\b"/g, replaceWith: '”'},    //  close quote (eg, is preceded by a 'word boundary') needs to be set to follow punctuation as well
	{ searchFor: /\b([\.|,|\?|!|;|:|-])"/g, replaceWith: '$1”'},    //  close quote after period (eg, is preceded by a 'word boundary')
	{ searchFor: /'(Twas|Tis|twas|tis)/g, replaceWith: '’$1'},    //  'Twas, 'tis [no longer handles /'tain't/ except as below, when following an open quote]
	{ searchFor: /t'other/g, replaceWith: "t’other"},    //  t(he) other  (Conrad 'Narcissus')
	{ searchFor: / -(-)? /g, replaceWith: " — "},    //  em dash between spaces
	{ searchFor: /(\w)-- /g, replaceWith: "$1— "},    //  em dash after character followed by space
	{ searchFor: /(\w)--'(\w)/g, replaceWith: "$1—‘$2"},    //  em dash after character followed by right single quote and space
	{ searchFor: /(\w)'--(\w)/g, replaceWith: "$1’—$2"},    //  right single quote after character followed by em dash
	{ searchFor: /(\w)'-(\w)/g, replaceWith: "$1’-$2"},    //  right single quote after character followed by hyphen and character (stun'-sails, Youth)
	{ searchFor: /(\w)--' /g, replaceWith: "$1—’ "},    //  em dash after character followed by right single quote and space
	{ searchFor: /(\w)(in)'(\s|,)/g, replaceWith: "$1$2’$3"},    //  dropped g in -ing (darlin') (Conrad 'Narcissus')
	{ searchFor: /(\s)(tho)'/g, replaceWith: "$1$2’"},    //  tho(ugh) (Conrad 'Narcissus')
	{ searchFor: /(\w)'; /g, replaceWith: "$1’; "},    //  apostrophe after character followed by semi-colon and space
	{ searchFor: /----(--)?"--("|“)/g, replaceWith: '——”—“'},    //  2-em dash ending quote then dash separating next quote (Conrad 'Narcissus')
	{ searchFor: /----(--)?/g, replaceWith: "——"},    //  2-em dash (Conrad 'Narcissus')
	{ searchFor: /^--("|“)/gm, replaceWith: '—“'},    //  line beginning with dash then open quote (Conrad 'Narcissus')
	{ searchFor: /(\?)--\b/g, replaceWith: "$1—"},    //  em dash after question mark followed by word boundary
	{ searchFor: /(\w)--"$/gm, replaceWith: '$1—”'},    //  em dash after character followed by right double quote at line end
	{ searchFor: /(\w)--'$/gm, replaceWith: "$1—’"},    //  em dash after character followed by right single quote at line end
	{ searchFor: /(\w|!|,|\?|”)--$/gm, replaceWith: "$1—"},    //  em dash after character at end of line
	{ searchFor: /(\w)--!\.\.\."$/gm, replaceWith: '$1—!...”'},    //  by--!..."  (Conrad 'Narcissus')
	{ searchFor: /(\w|!|,|\?|”)'--'/g, replaceWith: "$1’—‘"},    //  em dash after left single quote followed by right single quote
	{ searchFor: /(\w|!|,|\?|\.|”|:)--("|“)('(?=[EAIOUT]))/g, replaceWith: '$1—“’'},    //  dash and open double quote  followed by elided letter ('Tain't)
	{ searchFor: /(\w|!|,|\?|”)'--(\w)/g, replaceWith: "$1’—$2"},    //  em dash after left single quote followed by right single quote
	{ searchFor: /(\w)--(\w)--(\w)/gm, replaceWith: "$1—$2—$3"},    //  em dash after characters (or quote) followed by letter
	{ searchFor: /(\w|\.|”|\))--(\w|“|\s)/g, replaceWith: "$1—$2"},    //  em dash after characters (or quote) followed by letter
	{ searchFor: /(!|,|:|\.)--(\w|“|\s)/g, replaceWith: "$1—$2"},    //  em dash after punctuation and before characters (and between quotes)
	{ searchFor: /(:)--("')(\w)/gm, replaceWith: "$1—“‘$3"},    //  colon then dash then open double quote then open single quote then text   (Conrad 'Narcissus')
	{ searchFor: / '(Change)/g, replaceWith: " ’$1"},    //  'Change (Heart of Darkness)
	{ searchFor: /(\s)'e(e)?\b/g, replaceWith: "$1’e$2"},   //  space (h)e (Conrad 'Narcissus')
	{ searchFor: / '(\w)/g, replaceWith: " ‘$1"},    //  'Word (inner quote)
	{ searchFor: /(\.|\?|!)'\.\.\./g, replaceWith: "$1’..."},    //  close single quote after end punctuation followed by ellipsis
	{ searchFor: /(\w|,|\.|\?|!|;|:|-)' /g, replaceWith: "$1’ "},    //  close single quote after period etc; also "tot o' rum" (Conrad 'Narcissus')
	{ searchFor: /(\w|,|\.|\?|!|;|:|-)" /g, replaceWith: '$1” '},    //  close single quote after period etc
	{ searchFor: /(\w)'\? /g, replaceWith: "$1’? "},    //  close single quote before question mark  (Conrad 'Narcissus')
	{ searchFor: /(\w|,|\.|\?|!|;|:|-)'$/gm, replaceWith: "$1’"},    //  close single quote after period etc at end of line
	{ searchFor: /^(\s*)?'\b/gm, replaceWith: '$1‘'},    //  open single quote line beginning (eg, precedes a 'word boundary')
	{ searchFor: /^"--("|“)(\w)/gm, replaceWith: '”—“$2'},    //  dash separating quotes, but the close quote of the first starts a new line (Conrad 'Narcissus')
	{ searchFor: /(\s)"--("|“)(\w)/gm, replaceWith: '$1”—“$3'},    //  dash separating quotes, but the close quote of the first folos a space (Conrad 'Narcissus')
	{ searchFor: /(\w)'" /g, replaceWith: '$1’” '},    //  close single quote followed by close double quote and space
	{ searchFor: /^(\s*)?"'\b/gm, replaceWith: '$1“‘'},    //  open double quote preceding open single quote at line beginning (eg, precedes a 'word boundary')
	{ searchFor: /(\w)'\.\.\.$/g, replaceWith: "$1’..."},    //  close single quote after character followed by ellipsis
	{ searchFor: /(\w)(!)\.\.\.(\.|!)?"$/gm, replaceWith: '$1$2...$3”'},    //  close double quote after ellipsis preceded by char or ! (Youth)
	{ searchFor: /(\w)\.\.\.(\.)?"--("|“)/gm, replaceWith: '$1...$2”—“'},    //  close double quote (preceded by three or four dots) then a dash then an open quote  (Conrad 'Narcissus')
	{ searchFor: /^(\s*)?"\.\.\./gm, replaceWith: '$1“...'},    //  open quote at line beginning, followed by ellipsis
	{ searchFor: /^(\s*)"\.\.\./gm, replaceWith: '$1“...'},    //  spaces then open quote at line beginning, followed by ellipsis (Youth)
	{ searchFor: /^(\s*)?"'\.\.\./gm, replaceWith: '$1“‘...'},    //  open quote then open single quote at line beginning, followed by ellipsis
	{ searchFor: /(D|d)'y(e|ou)/g, replaceWith: "$1’y$2"},   //  d'ye (Heart of Darkness), d'you (Conrad 'Narcissus')
	{ searchFor: /(R|r)eg'lar/g, replaceWith: "$1eg’lar"},   //  reg'lar (Conrad 'Narcissus')
	{ searchFor: /bo'sen/g, replaceWith: "bo’sen"},   //  bosun (Conrad 'Narcissus')
	{ searchFor: /Boss'en/g, replaceWith: "Boss’en"},   //  bosun (Conrad 'Narcissus')
	{ searchFor: /(e|r)'n/g, replaceWith: "$1’n"},   //  more (tha)n (Conrad 'Narcissus')
	//{ searchFor: /(better|more|rather|worser|wusse)'n/g, replaceWith: "$1’n"},   //  more (tha)n (Conrad 'Narcissus')
	{ searchFor: /ha'penny/g, replaceWith: "ha’penny"},   //  ha(lf)penny (Conrad 'Narcissus')
	{ searchFor: /(\s|“)'(Ee|ee|Ave)/g, replaceWith: "$1’$2"},   //  space open quote (H)e (Conrad 'Narcissus')
	{ searchFor: /(\s)"'(Ee|ee|Ave)/g, replaceWith: "$1“’$2"},   //  space open quote (H)e (Conrad 'Narcissus')
	{ searchFor: /^'ee/gm, replaceWith: "’ee"},   //  (h)e at line beginning (Conrad 'Narcissus')
	{ searchFor: /voy'ge/g, replaceWith: "voy’ge"},   //  voy(a)ge (Conrad 'Narcissus')
	{ searchFor: /(:|,) '_(\w)/g, replaceWith: "$1 ‘_$2"},   //  italic inside single quote, markdown (Youth)
	{ searchFor: / '<i>/g, replaceWith: " ‘<i>"},   //  italic inside single quote, html (Heart of Darkness)
	{ searchFor: /([a-zA-Z])'([\.;-?!:])/, replaceWith: "$1’$2" }
];

var samwilsonCurlyQuotes = function() {
	$( '#wpTextbox1' ).wikiEditor( 'addToToolbar', {
		'section': 'main',
		'group': 'format',
		'tools': {
			'smile': {
				label: 'Use curly quotes', 
				type: 'button',
				icon: '//upload.wikimedia.org/wikipedia/commons/a/a1/Straight-2-curly-quotes-toolbutton.png',
				action: {
					type: 'callback',
					execute: function () {
						mw.log( 'Applying curly quotes' );
						var text = $("#wpTextbox1").val();
						punctuationReplacements.forEach(function(replacement) {
							text = text.replace(replacement.searchFor, replacement.replaceWith);
						});
						$("#wpTextbox1").val(text);
					}
				}
			}
		}
	} );
};

/* Check if view is in edit mode and that the required modules are available. Then, customize the toolbar … */
if ( $.inArray( mw.config.get( 'wgAction' ), [ 'edit', 'submit' ] ) !== -1 ) {
	mw.loader.using( 'user.options', function () {
		if ( mw.user.options.get( 'usebetatoolbar' ) == 1 ) {
			var dependencies = [ 'ext.wikiEditor' ];
			if ( mw.user.options.get( 'codemirror-syntax-highlight' ) == 1 ) {
				dependencies.push( 'ext.CodeMirror.lib' );
			}
			mw.log( 'Loading [[User:Samwilson/CurlyQuotes.js]]' );
			mw.loader.using( dependencies, $.ready ).then( samwilsonCurlyQuotes );
		}
	} );
}