Humble Origins: PolishMyWriting.com
I found an old screenshot today and thought I’d share it to give you an idea of (1) how bad my design eye is and (2) some history of After the Deadline. After the Deadline started life as a web-based style checker hosted at PolishMyWriting.com. My goal? Convince people to paste in their documents to receive feedback on the fly, while I made tons of money from ad revenue. It seemed like a good idea at the time.
PolishMyWriting.com did not check spelling, misused words, or grammar. It relied on 2,283 rules to look for phrases to avoid, jargon terms, biased phrases, clichés, complex phrases, foreign words, and redundant phrases. The most sophisticated natural language processing in the system detected passive voice and hidden verbs.I wouldn’t call it sophisticated though. I referenced a chart and wrote simple heuristics to capture the different forms of passive voice. Funny, it’s the same passive voice detector used in After the Deadline today.
This rule-based system presents all of its suggestions (with no filtering or reordering) to the end-user. A hacker news user agreed with 50% of what it had to say and that’s not too bad. After the Deadline today looks at the context of any phrase it flags and tries to decide whether a suggestion is appropriate or not. A recent reviewer of After the Deadline says he agrees with 75% of what it says. An improvement!
How PolishMyWriting.com Worked
My favorite part of PolishMyWriting.com was how it stored rules. All the rules were collapsed into a tree. From each word position in the document, the system would the tree looking for the deepest match. In this way PolishMyWriting.com only had to evaluate rules that were relevant to a given position in the text. It was also easy for the match to fail right away (hey, the current word doesn’t match any of the possible starting words for a rule). With this I was able to create as many rules as I liked without impacting the performance of the system. The rule-tree in After the Deadline today has 33,331 end-states. Not too bad.
The rule-tree above matches six rules. If I were to give it a sentence: I did not remember to write a lot of posts. The system would start with I and realize there is nowhere to go. The next word is did. It would look at did and check if any children from did in the tree match the word after did in the sentence. In this case not matches. The system repeats this process from not. The next word that matches is succeed. Here PolishMyWriting.com would present the suggestions for did not succeed to the user. If the search would have failed to turn up an end state for did not succeed, the system would advance to not and repeat the process from the beginning. Since it found a match, the process would start again from to write a lot of posts. The result I [forgot] to write [many, much] blog posts.
What happened to PolishMyWriting.com?
I eventually dumped the PolishMyWriting.com name. Mainly because I kept hearing jokes from people online (and in person) about how great it was that I developed a writing resource for the Polish people. It stills exists, mainly as a place to demonstrate After the Deadline.
And don’t forget, if PolishMyWriting.com helps you out.. you can link to it using our nifty banner graphic.
Rethink Your Relationship with Your Spell Checker
Last week, switched.com reviewed several grammar checkers to celebrate National Grammar Day. The tested text was interesting to me and it inspired this post.
Its common for users to rely entirely on the in built proofreading capabilities of a word processor. Since the technology became standard in Microsofts Word in the 90′s countless cubicle dwellers and students have stopped carefully proofreading they’re own writing they have instead trust the automated spellcheck and grammar correcting features of their office product of choice to identify errors. We have carefully crafted this text to test the accuracy of these features, there are roughly 10 common grammatical mistakes in this paragraph. No matter good these tools perform there no replacement for carefully rereading you’re writing.
I agree and I think it’s time people rethink their relationship with their spell checker.
My friend Karen once told me a story about giving her husband feedback on a school paper. She noticed that he really liked semicolons. She confronted him on this and he said that Microsoft Word kept suggesting them and he kept accepting them. This is not a good situation.
Many writers rely on their spell checker to a fault. They see their spell checker as a tool to verify that a document is correct and ready to go with no effort on their part. If you want to verify that a document is correct, you need to reread it and look for errors. A great technique is to read the document backwards. Purdue’s Online Writing Lab has more tips like this.
If writers need to reread their documents, then what is the use of tools like After the Deadline? I look at After the Deadline as a tool that teaches users about writing. When asked what I do, I sometimes reply that I’m an English teacher with many thousands of students. No one gets the joke. It’s ok.
After the Deadline does a good job of finding its/it’s errors. It does not find all of them. I think this is OK. If a user checks their document and has a habit of misusing its/it’s, they’ll probably see a lot of errors. If this user is inquisitive, they may quick click explain. By doing this they’ll learn why the error is an error. By reading the feedback during the writing process, the lesson has the most potential to sink in.
Feedback is most valuable when it’s immediate. After the Deadline makes you a better writer through immediate feedback.

All About Language Models
One of the challenges with most natural language processing tasks is getting data and collapsing it into a usable model. Prepping a large data set is hard enough. Once you’ve prepped it, you have to put it into a language model. My old NLP lab (consisting of two computers I paid $100 for from Cornell University), it took 18 hours to build my language models. You probably have better hardware than I did.
Save the Pain
I want to save you some pain and trouble if I can. That’s why I’m writing today’s blog post. Did you know After the Deadline has prebuilt bigram language models for English, German, Spanish, French, Italian, Polish, Indonesian, Russian, Dutch, and Portuguese? That’s 10 languages!
Also, did you know that the After the Deadline language model is a simple serialized Java object. In fact, the only dependency to use it is one Java class. Now that I’ve got you excited, let’s ask… what can you do with a language model?
Language Model API
A bigram language model has the count of every sequence of two words seen in a collection of text. From this information you can calculate all kinds of interesting things.
As an administrative note, I will use the Sleep programming language for these examples. This code is trivial to port to Java but I’m on an airplane and too lazy to whip out the compiler.
Let’s load up the Sleep interactive interpreter and load the English language model. You may assume all these commands are executed from the top-level directory of the After the Deadline source code distribution.
$ java -Xmx1536M -jar lib/sleep.jar
>> Welcome to the Sleep scripting language
> interact
>> Welcome to interactive mode.
Type your code and then '.' on a line by itself to execute the code.
Type Ctrl+D or 'done' on a line by itself to leave interactive mode.
import * from: lib/spellutils.jar;
$handle = openf('models/model.bin');
$model = readObject($handle);
closef($handle);
println("Loaded $model");
.
Loaded org.dashnine.preditor.LanguageModel@5cc145f9
done
And there you have it. A language model ready for your use. I’ll walk you through each API method.
Count Words
The count method returns the number of times the specified word was seen.
Examples:
> x [$model count: "hello"] 153 > x [$model count: "world"] 26355 > x [$model count: "the"] 3046771
Word Probability
The Pword method returns the probability of a word. The Java way to call this is model.Pword(“word”).
> x [$model Pword: "the"] 0.061422322118108906 > x [$model Pword: "Automattic"] 8.063923690767558E-7 > x [$model Pword: "fjsljnfnsk"] 0.0
Word Probability with Context
That’s the simple stuff. The fun part of the language model comes in when you can look at context. Imagine the sentence: “I want to bee an actor”. With the language model we can compare the fit of the word bee with the fit of the word be given the context. The contextual probability functions let you do that.
Pbigram1(“previous”, “word”)
This method calculates P(word|previous) or the probability of the specified word given the previous word. This is the most straight forward application of our bigram language model. After all, we have every count of “previous word” that was seen in the corpus we trained with. We simply divide this by the count of previous to arrive at an answer. Here we use our contextual probability to look at be vs. bee.
> x [$model Pbigram1: "to", "bee"] 1.8397294594205855E-5 > x [$model Pbigram1: "to", "be"] 0.06296975819264979
Pbigram2(“word”, “next”)
This method calculates P(word|next) or the probability of the specified word given the next word. How does it do it? It’s a simple application of Bayes Theorem. Bayes Theorem lets us flip the conditional in a probability. It’s calculated as: P(word|next) = P(next|word) * P(word) / P(next). Here we use it to further investigate the probability of be vs. bee:
> x [$model Pbigram2: "bee", "an"] 0.0 > x [$model Pbigram2: "be", "an"] 0.014840446919206074
If you were a computer, which word would you assume the writer meant?
A Little Trick
These methods will also accept a sequence of two words in the parameter that you’re calculating the probability of. I use this trick to segment a misspelled word with a space between each pair of letters and compare the results to the other spelling suggestions.
> x [$model Pword: "New York"] 3.3241509434266565E-4 > x [$model Pword: "a lot"] 2.1988303923800437E-4 > x [$model Pbigram1: "it", "a lot"] 8.972553689218159E-5 > x [$model Pbigram2: "a lot", '0END.0'] 6.511467636360339E-7
You’ll notice 0END.0. This is a special word. It represents the end of a sentence. 0BEGIN.0 represents the beginning of a sentence. The only punctuation tracked by these models is the ','. You can refer to it directly.
Harvest a Dictionary
One of my uses for the language model is to dump a spell checker dictionary. I do this by harvesting all words that occur two or more times. When I add enough data, I’ll raise this number to get a higher quality dictionary. To harvest a dictionary:
> x [$model harvest: 1000000] [a, of, to, and, the, 0END.0, 0BEGIN.0]
This command harvests all words that occur a million or more times. As you can see, there aren’t too many. The language model I have now was derived 75 million words of text.
The Next Step
That’s the After the Deadline language model in a nutshell. There is also a method to get the probability of a word given the two words that came before it. This is done using trigrams. I didn’t write about it here because AtD stores trigrams for words tracked by the misused word detector only.
That said, there’s a lot of fun you can have with this kind of data.
Download the After the Deadline open source distribution. You’ll find the English language model at models/model.bin. You can also get spellutils.jar from the lib directory.
If you want to experiment with bigrams in other languages, the After the Deadline language pack has the trained language models for nine other languages.
The English language model was trained from many sources including Project Gutenberg and Wikipedia. The other language models were trained from their respective Wikipedia dumps.
Good luck and have fun.
After the Deadline is an open source grammar, style, and spell checker. Unlike other tools, it uses context to make smart suggestions for errors. Plugins are available for Firefox, WordPress, and others.
AtD Firefox 1.1 Released – Write Right in More Places
After the Deadline for Firefox 1.1 is now live on addons.mozilla.org. After the Deadline for Firefox lets you check your spelling, style, and grammar where ever you are on the web.
This release of After the Deadline for Firefox works in more places. Here is a screenshot of After the Deadline working with Google Docs:
This release also:
- Adds proofreading for French, German, Portuguese, and Spanish
- Fixes several bugs and reported add-on conflicts
You can read the full list of changes at http://firefox.afterthedeadline.com/upgrades/1.1/
Proofread More Languages + Good-bye API Keys
So many things to announce, how do I do it in one blog post? Let’s do a list. Drum role roll please.
5. Good-bye API keys
We’ve gotten rid of the AtD API keys. I was pushing to ask for more information and force folks to download a white paper before getting anything. Needless to say, I lost that battle. Using After the Deadline no longer requires registering with us. It’s still free for personal use. If you have a commercial need, grab our open source software.
4. Open Source Software – Updated Release
Finally, after all this time, After the Deadline’s server software is in a public subversion repository. We’ve also repackaged the current code and updated some of the documentation. Now you can check out the server software and stay in sync with what we’re using. We also have a mechanism (a local.sl file) where you can make local changes and not worry about us breaking them during future updates.
3. AtD speaks multiple languages
Yes, now AtD speaks multiple languages. We’ve put servers in place for French, German, Portuguese, and Spanish. We have more languages ready to go and we’ll make those available in the future. We’re providing contextual spell checking for these languages. French and German have grammar checking courtesy of the excellent Language Tool project. Misused word detection is under development.
The AtD Language Pack on our open source server page has everything you need.
2. bbPress Plugin Update
As if some otherworldly power was driving him, Gautam released an update to AtD/bbPress with support for French, German, Portuguese, and Spanish on Friday. How he knew about all this stuff before us, I don’t know
But it’s great and if you use bbPress you need to get the plugin.
1. WordPress Plugin Update with Translations
And yes, our WordPress plugin has been updated to banish the API key nag-screen and to support proofreading in French, German, Portuguese, and Spanish.
The updated WordPress plugin uses your WPLANG setting to decide which language it should proofread in. If you blog in many languages or this setting doesn’t work for you, visit your profile page (the same place where all the AtD settings are) and enable the proofread with detected language option. With this turned on, After the Deadline will detect your language and apply the correct proofreader to it.
Thanks to the wonderful WordPress community volunteers, the AtD plugin has translations for Portuguese, Hindi, Japanese, French, Finnish, Bosnian, and Persian.
0. An Extra Bonus
I originally wanted to provide 10 exciting news items and this post became way too long with too much stuff at the top. So now you get a bonus item. We’ve also released updates to the AtD front-end components. They’re L10n ready and AtD/jQuery is now compatible with jQuery 1.4.
N-Gram Language Guessing with NGramJ
NGramJ is a Java library for language recognition. It uses language profiles (counts of character sequences) to guess what language some arbitrary text is. In this post I’ll briefly show you how to use it from the command-line and the Java API. I’ll also show you how to generate a new language profile. I’m doing this so I don’t have to figure out how to do it again.
Running
You can get a feel for how well NGramJ works by trying it on the command line. For example:
$ cat >a.txt This is a test. $ java -jar cngram.jar -lang2 a.txt speed: en:0.667 ru:0.000 pt:0.000 .. de:0.000 |0.0E0 |0.0E0 dt=2812 $ cat >b.txt Wikipedia ist ein Projekt zum Aufbau einer Enzyklopädie aus freien Inhalten in allen Sprachen der Welt. $ java -jar cngram.jar -lang2 b.txt speed: de:0.857 ru:0.000 pt:0.000 .. en:0.000 |0.0E0 |0.0E0 dt=2077
Using
Something I like about the API for this program–it’s simple. It is also thread-safe. You can instantiate a static reference for the library and call it from any thread later. Here is some code adopted from the Flaptor Utils library.
import de.spieleck.app.cngram.NGramProfiles;
protected NGramProfiles profiles = new NGramProfiles();
public String getLanguage(String text) {
NGramProfiles.Ranker ranker = profiles.getRanker();
ranker.account(text);
NGramProfiles.RankResult result = ranker.getRankResult();
return result.getName(0);
}
Now that you know how to use the library for language guessing, I’ll show you how to add a new language.
Adding a New Language
NGramJ comes with several language profiles but you may have a need to generate one yourself. A great source of language data is Wikipedia. I’ve written about extracting plain-text from Wikipedia here before. Today, I needed to generate a profile for Indonesian. The first step is to create a raw language profile. You can do this with the cngram.jar file:
$ java -jar cngram.jar -create id_big id_corpus.txt new profile 'id_big.ngp' was created.
This will create an id.ngp file. I also noticed this file is huge. Several hundred kilobytes compared to the 30K of the other language profiles. The next step is to clean the language profile up. To do this, I created a short Sleep script to read in the id.ngp file and cut any 3-gram and 4-gram sequences that occur less than 20K times. I chose 20K because it leaves me with a file that is about 30K. If you have less data, you’ll want to adjust this number downwards. The other language profiles use 1000 as a cut-off. This leads me to believe they were trained on 6MB of text data versus my 114MB of Indonesian text.
Here is the script:
%grams = ohash();
setMissPolicy(%grams, { return @(); });
$handle = openf(@ARGV[0]);
$banner = readln($handle);
readln($handle); # consume the ngram_count value
while $text (readln($handle)) {
($gram, $count) = split(' ', $text);
if (strlen($gram) <= 2 || $count > 20000) {
push(%grams[strlen($gram)], @($gram, $count));
}
}
closef($handle);
sub sortTuple {
return $2[1] <=> $1[1];
}
println($banner);
printAll(map({ return join(" ", $1); }, sort(&sortTuple, %grams[1])));
printAll(map({ return join(" ", $1); }, sort(&sortTuple, %grams[2])));
printAll(map({ return join(" ", $1); }, sort(&sortTuple, %grams[3])));
printAll(map({ return join(" ", $1); }, sort(&sortTuple, %grams[4])));
To run the script:
$ java -jar lib/sleep.jar sortit.sl id_big.ngp >id.ngp
The last step is to copy id.ngp into src/de/spieleck/app/cngram/ and edit src/de/spieleck/app/cngram/profiles.lst to contain the id resource. Type ant in the top-level directory of the NGramJ source code to rebuild cngram.jar and then you’re ready to test:
$ cat >c.txt Selamat datang di Wikipedia bahasa Indonesia, ensiklopedia bebas berbahasa Indonesia $ java -jar cngram.jar -lang2 c.txt speed: id:0.857 ru:0.000 pt:0.000 .. de:0.000 |0.0E0 |0.0E0 dt=1872
As you can see NGramJ is an easy to work with library. If you need to do language guessing, I recommend it.
After the Deadline for Firefox – Released
We received addons.mozilla.org approval of After the Deadline recently and we’re pleased to announce the release of the After the Deadline add-on for Firefox.
After the Deadline works in text areas on most webpages. Simply push a button (F7) or click
to check your spelling, style, and grammar no matter where you are.
This add-on has all the After the Deadline features. You can enable the style checker options you use in the preferences and you can ignore errors to prevent them from coming up.
Links of interest:
- Download After the Deadline for Firefox
- View the documentation
- Visit the homepage: http://firefox.afterthedeadline.com
After the Deadline is an open source proofreading technology. You can also embed it into web applications using TinyMCE, jQuery, and CKEditor.
How I Trie to Make Spelling Suggestions
Spell checking is a three-step process. Check if a word is in a dictionary, generate potential suggestions, and then sort the suggestions–hopefully with the intended word on top. Dr. Peter Norvig wrote a great essay on this and I suggest you read it.
Knowing about this three-step process, can you guess what the slowest part of it is? It’s not the dictionary check. It’s not the sorting of the suggestions either. Generating the potential suggestions is the killer. Today I’ll discuss a clever way to use to speed this process up using Tries.
The Problem: How I Generate Suggestions
The AtD spelling corrector generates potential suggestions by considering all words within two edits of the misspelled word. An edit is inserting, deleting, or substituting a letter. Transposing two letters is also an edit. From my own tests I found 96% of the time the correct word is within two edits. 1.5% of the time the desired word is within three edits. Beyond that, who cares?
Why is this operation so slow? The naïve algorithm is to generate all possible edits to the misspelled word to get every edit within an edit distance of one. For a word of length n, we have 54n+25 edits assuming a lowercase alphabet of a-z. To get all words within an edit distance of two, apply all possible edits to the previously generated edits. This number gets big quickly and forget three edits using this algorithm.
The Trie
A Trie is a tree data structure for storing values associated with a key. Each character of the key represents a branch of the Trie. Retrieving a value consists of finding the branch associated with the first character of the key, chopping it off, and repeating this process until there is no key. Tries are nice because the common values of the keys are shared making them space efficient for storing things like dictionaries.
Here is the Sleep code to build a Trie:
sub trie {
local('$trie $word $x $root $letter');
$trie = %();
foreach $word ($1) {
$root = $trie;
for ($x = 0; $x < strlen($word); $x++) {
$letter = charAt($word, $x);
if ($letter !in $root) {
$root[$letter] = %();
}
$root = $root[$letter];
}
$root['__'] = $word;
}
return $trie;
}
This function creates a Trie represented as a Sleep hash. Each branch in the Trie has letters which point to other Trie branches. A branch that represents a word will have a __ key with the associated word.
A word is added to the Trie by looking for the first letter of the word in the root branch. If the letter is not there, a new branch is made. This first letter is then removed from the word and the branch associated with the letter becomes the root branch. This process is repeated until there are no letters left in the word. At this point the __ key is set to mark that this branch represents a complete word.
$ java -jar lib/sleep.jar
>> Welcome to the Sleep scripting language
>
> interact
>> Welcome to interactive mode.
Type your code and then '.' on a line by itself to execute the code.
Type Ctrl+D or 'done' on a line by itself to leave interactive mode.
include("trie.sl");
$trie = trie(@("apple", "bat", "bart", "battle", "cat"));
println($trie);
.
%(b =>
%(a =>
%(t =>
%(t =>
%(l =>
%(e =>
%(__ => 'battle')
)
),
__ => 'bat'),
r =>
%(t =>
%(__ => 'bart')
)
)
),
c => %(a => %(t => %(__ => 'cat'))),
a => %(p => %(p => %(l => %(e => %(__ => 'apple')))))
)
If you’ve been exposed to Tries before, you’ve probably seen them used to check if a word is in the dictionary or not. Here is the Sleep code to test a word:
sub inTrie {
local('$x $word $trie $letter');
($word, $trie) = @_;
for ($x = 0; $x < strlen($word); $x++) {
$letter = charAt($word, $x);
if ($letter !in $trie) {
return;
}
$trie = $trie[$letter];
}
return iff ($trie['__'] eq $1);
}
This function walks the Trie using a process similar to adding a word. If there is no branch for the current letter then the test fails. If we’ve walked through all the letters, the __ key is checked to see if we indeed have a word match.
Typically __ doesn’t contain the word. It’s usually a boolean flag. I’m lazy though and put the word in this slot to make debugging the Trie code easier and to make it easier for me when testing if a word is in the Trie.
Generating Suggestions
AtD uses this Trie structure to generate all edits for a misspelled word. The Trie is nice because I only have to traverse paths that may yield words and I don’t have to keep a lot of traversal information in memory. Here is the code:
sub edits {
local('$trie $word $results $depth $root $branch $letter');
($trie, $word, $results, $depth) = @_;
if (strlen($word) == 0 && $depth >= 0 && $trie['__'] ne '') {
$results[ $trie['__'] ] = 1;
}
if ($depth >= 1) {
# deletion. [remove the current letter, and try it on the current branch--see what happens]
if (strlen($word) > 1) {
edits($trie, substr($word, 1), $results, $depth - 1);
}
else {
edits($trie, "", $results, $depth - 1);
}
foreach $letter => $branch ($trie) {
if ($letter eq '__') { continue; }
# insertion. [pass the current word, no changes, to each of the branches for processing]
edits($branch, $word, $results, $depth - 1);
# substitution. [pass the current word, sans first letter, to each of the branches for processing]
if (strlen($word) > 1) {
edits($branch, substr($word, 1), $results, $depth - 1);
}
else {
edits($branch, "", $results, $depth - 1);
}
}
# transposition. [swap the first and second letters]
if (strlen($word) > 2) {
edits($trie, charAt($word, 1) . charAt($word, 0) . substr($word, 2), $results, $depth - 1);
}
else if (strlen($word) == 2) {
edits($trie, charAt($word, 1) . charAt($word, 0), $results, $depth - 1);
}
}
# move on to the next letter. (no edits have happened)
if (strlen($word) >= 1 && charAt($word, 0) in $trie) {
$letter = charAt($2, 0);
if (strlen($word) > 1) {
edits($trie[$letter], substr($word, 1), $results, $depth);
}
else if (strlen($word) == 1) {
edits($trie[$letter], "", $results, $depth);
}
}
# results are stored in a hash to prevent duplicate words
return keys($results);
}
This function considers four types of edits: deletion, insertion, substitution, and transposition. This function returns a list of words within N edits from the specified word. It does this by considering the first letter of the word, the rest of the word, and the current branch of the Trie.
It handles deletions by applying this function to the current Trie branch and the rest of the word. In this way, all edits that could be generated from this letter being deleted are found.
With each call to edits, the function subtracts 1 from the max depth counter. When the max depth value is zero, no more traversals into the Trie are allowed. This counter makes sure we generate only words within the desired number of edits.
Insertion iterates through all child branches (representing each possible next letter) and applies the edit function to those branches with the current as an argument. In this way each branch is treated as a possible insertion before the current letter in the word.
Substitution iterates through each child branch and applies the edit function to those branches with the rest of the word as an argument.
Transposition swaps the first and second letters of the current word and applies the edit function on this new word using the current branch.
A Trie branch that maps to a word is added to the suggestion pool when it’s called with an empty string for the word and the depth counter is greater than zero (meaning we’re within the allowed number of edits).
Once these steps have completed, the algorithm finds the Trie branch for the next letter in the word, and calls edits with the branch for that letter as the Trie and the rest of the word. The depth counter isn’t changed as no edits were made, this is just advancing the algorithm to the next letter.
Here is a printout of edits applied to ‘sucess’ using a Trie constructed from the public domain 1939 Webster’s dictionary.
$ java -jar lib/sleep.jar
>> Welcome to the Sleep scripting language
> interact
>> Welcome to interactive mode.
Type your code and then '.' on a line by itself to execute the code.
Type Ctrl+D or 'done' on a line by itself to leave interactive mode.
include("trie.sl");
$dictionary = readAll(openf("/usr/share/dict/words"));
println(size($dictionary));
.
98569
$trie = trie($dictionary);
printAll(edits($trie, "sucess", %(), 2));
.
surest
sunless
sauces
sucks
sauce's
excess
recess
stress
Luce's
sices
access
duress
saucers
duchess
supers
guess
suers
suckers
success
sues
I’ve found this method is faster than the naïve edits generation algorithm. Let me know how it works for you.
This code is from the After the Deadline software service which is available under the LGPL license. You can find the code to the rest of the system at http://open.afterthedeadline.com
AtD at the DC CiviCRM Meetup
Washington, DC is full of non-profit organizations, advocacy groups, and NGOs. Last night I had a chance to meet with a group of CiviCRM enthusiasts. CiviCRM is an open source tool to help non-profits manage their donors. My goal was to spread the AtD love and talk about how one could add grammar and spell checking to their installation.
The instructions to add AtD to CiviCRM are:
- Extract atd-ckeditor.tgz into civicrm/packages/ckeditor/plugins
- I just noticed, the slides are wrong, it has to go into the plugins directory
- Edit civicrm/packages/HTML/QuickForm/ckeditor.php and follow slide 23.
- Run the Open Source AtD Service (it’s RAM hungry)
- Edit civicrm/packages/ckeditor/plugins/atd-ckeditor/proxy.php to point to your AtD server.
As you can see, it requires some hacking but it is possible. The same process for adding AtD to an app like CiviCRM is also possible for other applications as well. AtD has front-end components for TinyMCE, jQuery, and CKEditor.
The slides are here:
Coming Soon: Firefox Add-on
After the Deadline already can be easily embedded and can travel with you as a bookmarklet, but what if you could have After the Deadline’s superior grammar and spell check incorporated into the browser? We’re going to make this happen with the After the Deadline Firefox add-on. The Firefox add-on will let you use After the Deadline on web forms across the web, while providing the clean integration only possible through an add-on.
While there is still much work to be done on the add-on, a very early-stage beta of the add-on is now available for download. Please install the add-on and give it a try! If you find a bug or have a feature request, please post it to our trac bug tracker or email us. We’ll look forward to hearing your feedback!






2 comments