<turbo-stream action="append" target="posts_list"><template>    <div class="postbit" id="181268" data-post-id="181268">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="sorentwo" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/sorentwo/120/37360_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  sorentwo
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>Oban Core Team</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I (we, at dscout) definitely have a usecase for nearly all of this work . I hope to contribute in the future, and would love to support the effort financially if you decide to make that possible <img src="https://forum.elixirforum.com/images/emoji/apple/yellow_heart.png?v=15" title=":yellow_heart:" class="emoji" alt=":yellow_heart:" loading="lazy" width="20" height="20">.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="181268" data-batch-url="/posts/batch_likers">
                        6
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/text-a-text-analysis-library/32363/12">Post #11</a>
	                </div>
	            </div>
              <div id="likers-container-181268" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="181268"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #11"></div>
  </section>
</div>
    <div class="postbit" id="181276" data-post-id="181276">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="kip" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/kip/120/1440_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  kip
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>ex_cldr Core Team</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Just a little fun addition over coffee this morning - <a href="https://github.com/elixir-cldr/cldr-text" rel="nofollow">deriving a CLDR locale from natural language</a>.  I’ll publish it to hex after I add some tests.</p>
<h4><a name="p-181276-examples-1" class="anchor" href="#p-181276-examples-1" aria-label="Heading link" rel="nofollow"></a>Examples</h4>
<pre data-code-wrap="elixir"><code class="lang-elixir">iex&gt; Cldr.Text.locale_from_text "this is some text that I think will be English"
{:ok,                                                                                                                            %Cldr.LanguageTag{   
   backend: MyApp.Cldr,
   canonical_locale_name: "en-Latn-US",
   cldr_locale_name: "en",
   extensions: %{},
   gettext_locale_name: nil,
   language: "en",
   language_subtags: [],
   language_variant: nil,
   locale: %{},
   private_use: [],
   rbnf_locale_name: "en",
   requested_locale_name: "en",
   script: "Latn",
   territory: :US,
   transform: %{}
 }}

iex&gt; german_text = "Wir wohnen in einem kleinen Haus mit einem Garten. Dort können die Kinder ein bisschen spielen. Unser Sohn kommt bald in die Schule, unsere Tochter geht noch eine Zeit lang in den Kindergarten. Meine Kinder sind am Nachmittag zu Hause. So arbeite ich nur halbtags."
iex&gt; Cldr.Text.locale_from_text german_text
{:ok,                                                                                                                            %Cldr.LanguageTag{   
   backend: MyApp.Cldr,
   canonical_locale_name: "de-Latn-DE-1901",
   cldr_locale_name: "de",
   extensions: %{},
   gettext_locale_name: nil,
   language: "de",
   language_subtags: [],
   language_variant: "1901",
   locale: %{},
   private_use: [],
   rbnf_locale_name: "de",
   requested_locale_name: "de-1901",
   script: "Latn",
   territory: :DE,
   transform: %{}
 }}
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="181276" data-batch-url="/posts/batch_likers">
                        8
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/text-a-text-analysis-library/32363/13">Post #12</a>
	                </div>
	            </div>
              <div id="likers-container-181276" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="181276"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #12"></div>
  </section>
</div>
    <div class="postbit" id="184774" data-post-id="184774">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="rengel" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/rengel/120/20073_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  rengel
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Just stumbled upon this post. In case you didn’t konw:</p><aside class="onebox allowlistedgeneric" data-onebox-src="https://pypi.org/project/Metaphone/">
  <header class="source">

      <a href="https://pypi.org/project/Metaphone/" target="_blank" rel="noopener nofollow ugc">pypi.org</a>
  </header>

  <article class="onebox-body">
    

<h3><a href="https://pypi.org/project/Metaphone/" target="_blank" rel="noopener nofollow ugc">Client Challenge</a></h3>



  </article>

  <div class="onebox-metadata">
    
    
  </div>

  <div style="clear: both"></div>
</aside>
 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="184774" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/text-a-text-analysis-library/32363/14">Post #13</a>
	                </div>
	            </div>
              <div id="likers-container-184774" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="184774"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #13"></div>
  </section>
</div>
    <div class="postbit" id="184775" data-post-id="184775">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="kip" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/kip/120/1440_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  kip
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>ex_cldr Core Team</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>Thanks much for the link.  I’m a bit challenged reading these imperative implementations for two reasons:  (a) such ugly code compared to using pattern matching for most of it as one would in Elixir and (b) as a result, I just want the rules.  Megaphone I can find them, but not double.</p>
<p>Maybe I’ll do a basic Metaphone implementation first and at least move forward …</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="184775" data-batch-url="/posts/batch_likers">
                        0
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/text-a-text-analysis-library/32363/15">Post #14</a>
	                </div>
	            </div>
              <div id="likers-container-184775" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="184775"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #14"></div>
  </section>
</div>
    <div class="postbit" id="185379" data-post-id="185379">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="tfwright" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/tfwright/120/20299_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  tfwright
                  </h3>
		          </div>
						
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I’m really interested in using this library in a project of mine, in particular to generate something similar to “word clouds” where common <em>significant</em> words are highlighted. Is that something you are planning on supporting? Please let me know if there’s any part I can help out with.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="185379" data-batch-url="/posts/batch_likers">
                        1
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/text-a-text-analysis-library/32363/16">Post #15</a>
	                </div>
	            </div>
              <div id="likers-container-185379" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="185379"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #15"></div>
  </section>
</div>
    <div class="postbit" id="387916" data-post-id="387916">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="kip" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/kip/120/1440_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  kip
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>ex_cldr Core Team</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<blockquote>
<p>I’ll shortly be launching <a href="https://github.com/kipcole9/text" rel="nofollow">Text</a>, a nascent text analysis library.</p>
</blockquote>
<p>Well, instead of <em>shortly</em>, what I should have said is <em>in about 6 years</em>. But fear not:</p>
<p></p><div class="lightbox-wrapper"><a class="lightbox" href="https://forum.elixirforum.com/uploads/default/original/3X/f/5/f593d49908c02e4aff7d5084478c961edb4cc7ba.jpeg" data-download-href="https://forum.elixirforum.com/uploads/default/f593d49908c02e4aff7d5084478c961edb4cc7ba" title="Its-not-dead-its-resting-meme-generator-e1597175206791" rel="nofollow"><img src="https://forum.elixirforum.com/uploads/default/original/3X/f/5/f593d49908c02e4aff7d5084478c961edb4cc7ba.jpeg" alt="Its-not-dead-its-resting-meme-generator-e1597175206791" data-base62-sha1="z2tHKaJ2ErWyAGO69AztWVm2TI6" width="550" height="309"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">Its-not-dead-its-resting-meme-generator-e1597175206791</span><span class="informations">550×309 43.5 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div><p></p>
<p><em>See the <a href="https://www.youtube.com/watch?v=4vuW6tQ0218" rel="nofollow">YouTube video</a> if the meme isn’t familiar to you</em>.</p>
<p>A lot has change in the NLP world since 2020 and the new, modernised and thoroughly renovated <a href="https://hex.pm/packages/text/0.3.0" rel="nofollow">text 0.3.0</a> is now available on Hex.</p>
<h2><a name="p-387916-features-1" class="anchor" href="#p-387916-features-1" aria-label="Heading link" rel="nofollow"></a>Features</h2>
<h3><a name="p-387916-language-identification-2" class="anchor" href="#p-387916-language-identification-2" aria-label="Heading link" rel="nofollow"></a>Language identification</h3>
<p><code>Text.Language.Classifier.Fasttext</code> is a pure-Elixir port of <a href="https://fasttext.cc/docs/en/language-identification.html" rel="nofollow">lid.176</a>, validated bit-for-bit against fastText’s reference. 176 languages, ~100 µs per prediction with EXLA.</p>
<h3><a name="p-387916-sentiment-pos-ner-3" class="anchor" href="#p-387916-sentiment-pos-ner-3" aria-label="Heading link" rel="nofollow"></a>Sentiment, POS, NER</h3>
<p>Multilingual sentiment via bundled <a href="http://corpustext.com/reference/sentiment_afinn.html" rel="nofollow">AFINN</a> lexicons (default) or <a href="https://huggingface.co/docs/transformers/en/model_doc/xlm-roberta" rel="nofollow">XLM-RoBERTa</a> through Bumblebee (optional). Part-of-speech tagging and multilingual named-entity recognition via Bumblebee, with <code>mix text.download_models</code> to pre-fetch the weights at deploy time.</p>
<h3><a name="p-387916-word-clouds-4" class="anchor" href="#p-387916-word-clouds-4" aria-label="Heading link" rel="nofollow"></a>Word clouds</h3>
<p>A new <code>Text.WordCloud</code> module with six scoring backends — <a href="http://yake.inesctec.pt" rel="nofollow">YAKE!</a> by default, plus frequency, RAKE, TextRank, TF-IDF, and a KeyBERT variant for users who configure Bumblebee.</p>
<p><code>Text.WordCloud.Layout</code> does Wordle-style spiral packing (with <code>:radial</code> and <code>:spiral</code> orientation modes for sunburst and vortex looks), and <code>Text.WordCloud.SVG</code> produces renderer-agnostic output that plays nicely with <code>Color.Palette</code> for coordinated tonal-scale colour ramps. You can see some examples in <a href="https://hexdocs.pm/text/word_clouds.html" rel="nofollow">the guide</a>.</p>
<h3><a name="p-387916-fundamentals-5" class="anchor" href="#p-387916-fundamentals-5" aria-label="Heading link" rel="nofollow"></a>Fundamentals</h3>
<ul>
<li>String distance and similarity (Levenshtein, Damerau-Levenshtein, Jaro-Winkler, Jaccard, cosine, …)</li>
<li>Phonetic encoding (Soundex, Metaphone),</li>
<li>Unicode-aware segmentation, slug generation, TF-IDF and BM25 search,</li>
<li>Collocation extraction (PMI, log-likelihood), and</li>
<li>Keyword-in-context concordance.</li>
</ul>
<p>Also includes bundled stopwords for ~60 languages from stopwords-iso, and optional Snowball stemming via <code>:text_stemmer</code> to consolidate morphological variants in word clouds.</p>
<h3><a name="p-387916-optional-ml-6" class="anchor" href="#p-387916-optional-ml-6" aria-label="Heading link" rel="nofollow"></a>Optional ML</h3>
<p>Heavy ML deps (<code>:bumblebee</code>, <code>:exla</code>, KeyBERT) are all optional. Without them, the package still does most of what it does — just without the neural-quality ceiling. Same goes for <code>:color</code> (SVG palettes), <code>:localize</code> (CLDR locale resolution), and <code>:text_stemmer</code>.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="387916" data-batch-url="/posts/batch_likers">
                        6
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/text-a-text-analysis-library/32363/17">Post #16</a>
	                </div>
	            </div>
              <div id="likers-container-387916" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="387916"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #16"></div>
  </section>
</div>
    <div class="postbit" id="387917" data-post-id="387917">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="kip" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/kip/120/1440_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  kip
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>ex_cldr Core Team</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>There are two companion libraries also published today:</p>
<ul>
<li><a href="https://hex.pm/packages/snowball" rel="nofollow">Snowball</a> that implements the <a href="https://snowballstem.org" rel="nofollow">Snowball language</a> as a cross compiler to Elixir. Its a fun language with its roots in <a href="https://www.snobol5.com/intro1.htm" rel="nofollow">SNOBOL</a> but was designed by <a href="https://en.wikipedia.org/wiki/Martin_Porter" rel="nofollow">Porter</a> specifically to support the implementation of language stemmers. This is not a general purpose language or compiler - there are no conveniences at all. Just a mix task to take <code>.sbl</code> files and cross-compile them to Elixir.</li>
<li><a href="https://hex.pm/packages/text_stemmer" rel="nofollow">text_stemmer</a> which implements all <a href="https://snowballstem.org/algorithms/" rel="nofollow">37 stemming algorithms</a> - validated against their respective conformance suites.</li>
</ul>
<p><a href="https://hex.pm/packages/text_stemmer" rel="nofollow">text_stemmer</a> is an optional dependency for <a href="https://hex.pm/packages/text" rel="nofollow">text</a> which can be used to tune word clouds.</p>
<p>These two are 100% LLM generated.  It took 4 complete days for Claude (Sonnet) to implement. It’s a classic LLM opportunity. Formal specification, canonical implementations, complete conformance testing suites. Prompt the LLM and then go work on <a href="https://forum.elixirforum.com/t/what-do-you-do-while-waiting-for-it-claude-code/75143" rel="nofollow">something else</a>.</p> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="387917" data-batch-url="/posts/batch_likers">
                        7
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/text-a-text-analysis-library/32363/18">Post #17</a>
	                </div>
	            </div>
              <div id="likers-container-387917" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="387917"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #17"></div>
  </section>
</div>
    <div class="postbit" id="388014" data-post-id="388014">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="kip" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/kip/120/1440_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  kip
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>ex_cldr Core Team</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I’ve published <a href="https://hex.pm/packages/text/0.4.0" rel="nofollow">Text 0.4.0</a> today with seven new NLP modules (all native Elixir, no NIF or ML).</p>
<p>They cover the kinds of preprocessing you might reach for once your sentiment / classification / search pipeline outgrows <code>String.split/1</code>.</p>
<p>This release represents a largely feature complete text library from my perspective. Happy to take feature suggestions though.</p>
<p>A few of the more immediately useful additions in this release:</p>
<h3><a name="p-388014-textclean-pipeline-style-normalisation-1" class="anchor" href="#p-388014-textclean-pipeline-style-normalisation-1" aria-label="Heading link" rel="nofollow"></a><code>Text.Clean</code> — pipeline-style normalisation</h3>
<p>Whitespace, control characters, smart quotes, mojibake, NFC/NFKC. Composable; defaults are sensible.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">iex&gt; Text.Clean.clean("&lt;p&gt;itâ€™s   &lt;em&gt;cool&lt;/em&gt;&lt;/p&gt;")
"it's cool"
iex&gt; Text.Clean.collapse_whitespace("  hello \tworld  \n")
"hello world"
</code></pre>
<h3><a name="p-388014-texttruecase-restore-casing-for-all-caps-or-lowercased-text-2" class="anchor" href="#p-388014-texttruecase-restore-casing-for-all-caps-or-lowercased-text-2" aria-label="Heading link" rel="nofollow"></a><code>Text.Truecase</code> — restore casing for ALL-CAPS or lowercased text</h3>
<p>POS-aware heuristics for proper nouns, acronyms, and sentence starts. Useful when an upstream system has destroyed the casing (chat logs, OCR, screaming customer feedback).</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">iex&gt; Text.Truecase.truecase("THE QUICK BROWN FOX JUMPS OVER NEW YORK")
"The quick brown fox jumps over New York"
iex&gt; Text.Truecase.truecase("nasa launched apollo 11 in july 1969.")
"NASA launched Apollo 11 in July 1969."

# Add domain-specific terms once at boot
Text.Truecase.add_terms(["GraphQL", "Phoenix"])
Text.Truecase.truecase("we use phoenix and graphql")
#=&gt; "we use Phoenix and GraphQL"
</code></pre>
<h3><a name="p-388014-textemoji-detection-stripping-counting-conversion-3" class="anchor" href="#p-388014-textemoji-detection-stripping-counting-conversion-3" aria-label="Heading link" rel="nofollow"></a><code>Text.Emoji</code> — detection, stripping, counting, conversion</h3>
<p>Backed by the <code>:unicode</code> package’s emoji property tables, so it recognises every codepoint flagged emoji in the current Unicode release — no shipped JSON.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">iex&gt; Text.Emoji.count("Loved it 🤩 read it twice 📚📚")
3
iex&gt; Text.Emoji.demojize("ship it 🚀")
"ship it :rocket:"
iex&gt; Text.Emoji.emojize("ship it :rocket:")
"ship it 🚀"
</code></pre>
<h3><a name="p-388014-texthyphenation-knuthliang-tex-pattern-hyphenation-4" class="anchor" href="#p-388014-texthyphenation-knuthliang-tex-pattern-hyphenation-4" aria-label="Heading link" rel="nofollow"></a><code>Text.Hyphenation</code> — Knuth–Liang TeX-pattern hyphenation</h3>
<p>Ships en-US patterns baked in (~5 000). Other languages load from any standard <code>hyph-*.tex</code> file.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">iex&gt; Text.Hyphenation.hyphenate("hyphenation")
"hy-phen-ation"
iex&gt; Text.Hyphenation.count("supercalifragilisticexpialidocious")
9
# Load German patterns once; thereafter all calls are fast
Text.Hyphenation.load_language(:de, path: "hyph-de-1996.tex")
Text.Hyphenation.hyphenate("Bundesausbildungsförderungsgesetz", language: :de)
#=&gt; "Bun-des-aus-bil-dungs-för-de-rungs-ge-setz"
</code></pre>
<h3><a name="p-388014-textpii-detect-redact-common-identifiers-5" class="anchor" href="#p-388014-textpii-detect-redact-common-identifiers-5" aria-label="Heading link" rel="nofollow"></a><code>Text.PII</code> — detect &amp; redact common identifiers</h3>
<p>Phone, email, credit-card-shaped digits, IBANs, IPv4/IPv6, US SSN. Pattern-based — fast and deterministic. The right tool for <em>“please don’t paste this into the LLM”</em> preflight; pair with a stricter checker if you need legal-grade accuracy.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">iex&gt; Text.PII.detect("Email me at jane@example.com or call (415) 555-0142.")
[%{type: :email, value: "jane@example.com",  offset: 12, length: 16},  %{type: :phone, value: "(415) 555-0142",    offset: 37, length: 14}]
iex&gt; Text.PII.redact("Card 4111-1111-1111-1111 expires 12/29")
"Card [CREDIT_CARD] expires 12/29"
</code></pre>
<h3><a name="p-388014-textspell-norvig-style-spelling-suggestions-6" class="anchor" href="#p-388014-textspell-norvig-style-spelling-suggestions-6" aria-label="Heading link" rel="nofollow"></a><code>Text.Spell</code> — Norvig-style spelling suggestions</h3>
<p>Edit-distance candidates ranked by frequency in <code>Text.WordFreq</code> (the 30,000-word English frequency table that also ships in 0.4.0).</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">iex&gt; Text.Spell.correct("speling")
"spelling"
iex&gt; Text.Spell.candidates("teh") |&gt; Enum.take(3)
[%{word: "the",  distance: 1, frequency: 6_187_267},  %{word: "tech", distance: 1, frequency:    49_320},  %{word: "ten",  distance: 1, frequency:    21_117}]
</code></pre>
<h3><a name="p-388014-textsummarize-extractive-summarisation-via-textrank-7" class="anchor" href="#p-388014-textsummarize-extractive-summarisation-via-textrank-7" aria-label="Heading link" rel="nofollow"></a><code>Text.Summarize</code> — extractive summarisation via TextRank</h3>
<p>Sentence-graph TextRank with configurable similarity (<code>:cosine</code> or <code>:jaccard</code>) and target length.</p>
<pre data-code-wrap="elixir"><code class="lang-elixir">article = """
The new bridge, opened on Tuesday, connects the two halves of the city for the first time in decades. Engineers worked three winters to anchor the central pier on the riverbed. Residents who used to take a 40-minute ferry now make the trip in five. The mayor said the project came in 2 % under budget, a rarity for civic work of this scale.
"""
iex&gt; Text.Summarize.summarize(article, sentences: 2)
"The new bridge, opened on Tuesday, connects the two halves of the city for the first time in decades. Residents who used to take a 40-minute ferry now make the trip in five."
</code></pre> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="388014" data-batch-url="/posts/batch_likers">
                        12
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/text-a-text-analysis-library/32363/19">Post #18</a>
	                </div>
	            </div>
              <div id="likers-container-388014" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="388014"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-standard-post cat-standard-post" title="Post #18"></div>
  </section>
</div>
    <div class="postbit" id="388149" data-post-id="388149">
  <section>
    <div class="post-wrap">


					<div class="post-header">
		        <div class="user-avatar">
		          <img alt="kip" src="https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/kip/120/1440_2.png" width="120" height="120" />
		        </div>
					
						<div class="user-details">
		          <div class="user-name">
		            <h3>
                  kip
                    <span class="op-star" title="Thread Starter">
                      <img alt="OP" class="op-star-icon" src="/assets/thread-icons/thread-icon-thread-starter-df91e872.png" />
                    </span>
                  </h3>
		          </div>
						
			          <div class="user-title">
									<span>ex_cldr Core Team</span>
			          </div>
						</div>
					
					</div>

	        <div class="thread-main">
	            <div class="post-body" data-turbo="false">
								<p>I’ve published a couple of updates in the last week. I think that pretty much all I need in a text analysis library but I’m more than happy to take feature requests.</p>
<h2><a name="p-388149-h-060-2026-05-04-1" class="anchor" href="#p-388149-h-060-2026-05-04-1" aria-label="Heading link" rel="nofollow"></a>[0.6.0] — 2026-05-04</h2>
<h3><a name="p-388149-added-2" class="anchor" href="#p-388149-added-2" aria-label="Heading link" rel="nofollow"></a>Added</h3>
<ul>
<li>
<p><code>Text.Extract</code> — twitter-text-quality URL and email extraction with full UTS <span class="hashtag-raw">#46</span> IDNA, IANA TLD validation, and UTR <span class="hashtag-raw">#39</span> single-script defence against homograph attacks. Public API is <code>urls/2</code>, <code>emails/2</code>, <code>all/2</code>, <code>split/2</code>, and <code>autolink/2</code>; options include <code>:require_scheme</code>, <code>:tld_mode</code>, <code>:eai</code>, <code>:strict_idn</code>, and <code>:twitter_quirks</code>.</p>
</li>
<li>
<p><code>Text.Extract.split/2</code> — splits text into an interleaved list of plain-string fragments and validated entity maps, byte-for-byte round-trippable to the original. The building block for custom rendering of extracted URLs/emails into anchors, mentions, badges, or link-preview cards.</p>
</li>
<li>
<p><code>Text.Extract.autolink/2</code> — wraps URLs and emails in HTML <code>&lt;a&gt;</code> anchors, returning <code>Phoenix.HTML.safe()</code> for drop-in Phoenix template use. Display text preserves the original Unicode (<code>bücher.de</code>); the <code>href</code> uses Punycode (<code>xn--bcher-kva.de</code>).</p>
</li>
<li>
<p><code>mix text.download_tlds</code> — refreshes the bundled IANA TLD list at <code>priv/extract/tlds.txt</code>. <code>--diff</code> previews added/removed entries; <code>--force</code> overwrites unconditionally.</p>
</li>
<li>
<p><code>Text.WordCloud.to_d3_cloud/2</code> — adapts <code>terms/2</code> output into the <code>[%{text, size}, …]</code> shape consumed by <a href="https://github.com/jasondavies/d3-cloud" rel="nofollow">d3-cloud</a>. Supports <code>:linear</code> (default) and <code>:sqrt</code> sizing; shares the <code>:font_size_range</code> vocabulary with <code>Text.WordCloud.Layout</code>.</p>
</li>
</ul>
<h2><a name="p-388149-h-050-2026-05-02-3" class="anchor" href="#p-388149-h-050-2026-05-02-3" aria-label="Heading link" rel="nofollow"></a>[0.5.0] — 2026-05-02</h2>
<h3><a name="p-388149-added-4" class="anchor" href="#p-388149-added-4" aria-label="Heading link" rel="nofollow"></a>Added</h3>
<ul>
<li>
<p><code>Text.Phonetic.NYSIIS</code> — New York State Identification and Intelligence System phonetic encoding (Taft, 1970). Designed as a Soundex successor for English personal-name matching; produces pronounceable letter codes rather than digits and is more discriminating than Soundex on common name variations.</p>
</li>
<li>
<p><code>Text.Phonetic.Cologne</code> — Kölner Phonetik (Postel, 1969), the German-language counterpart to Soundex. Optimized for German spelling variants — <code>Müller</code> / <code>Mueller</code> / <code>Muller</code> and <code>Meyer</code> / <code>Mayer</code> / <code>Maier</code> / <code>Meier</code> collapse to single codes.</p>
</li>
<li>
<p><code>Text.Phonetic.DoubleMetaphone</code> — Lawrence Philips’ Double Metaphone (2000), the de-facto standard for fuzzy English-name matching with non-Anglo origins. Returns a <code>{primary, alternate}</code> code pair so the same Anglicised name can match across multiple plausible pronunciations (e.g. <code>Smith</code> ↔ <code>Schmidt</code>, <code>Catherine</code> ↔ <code>Katherine</code>). Handles Germanic, Italian, Spanish, French, Greek, and Slavic patterns.</p>
</li>
<li>
<p><code>match?/2</code> (and <code>match?/3</code> where options apply) on every <code>Text.Phonetic.*</code> module for direct equality comparison without manual <code>encode/2 == encode/2</code> boilerplate. <code>Text.Phonetic.DoubleMetaphone.match?/3</code> checks all four primary/alternate combinations.</p>
</li>
<li>
<p><code>Text.Clean.unaccent/1</code> — strip diacritics and fold non-decomposable Latin letters (<code>Þ</code> → <code>Th</code>, <code>ß</code> → <code>ss</code>, <code>Æ</code> → <code>AE</code>, <code>ł</code> → <code>l</code>, <code>đ</code> → <code>d</code>) by delegating to <code>Unicode.Transform.LatinAscii.transform/1</code>. Also exposed as the <code>:unaccent</code> option on <code>Text.Clean.clean/2</code>.</p>
</li>
<li>
<p><code>Text.Distance</code> gains four set-based similarity metrics over character n-grams: <code>jaccard/3</code>, <code>sorensen_dice/3</code>, <code>tanimoto/3</code> (alias for <code>jaccard/3</code>), and <code>cosine/3</code>. All accept an <code>:n</code> option for configurable shingle size (default 2). Operate at the grapheme level for Unicode correctness.</p>
</li>
<li>
<p><code>Text.Inflect.En.singularize/2</code> and <code>Text.Inflect.En.singularize_noun/2</code> — invert the existing pluralizer. Combines reverse lookup of Conway’s irregular tables, explicit suffix rules for unambiguous English plural forms (<code>-ies</code>, <code>-shes</code>/<code>-ches</code>/<code>-xes</code>/<code>-zes</code>/<code>-sses</code>), small whitelists for Greek-derived <code>-is</code>/<code>-es</code> plurals (<code>analyses → analysis</code>) and English <code>-us</code> plurals (<code>geniuses → genius</code>), and a <code>pluralize/2</code> round-trip search to validate other candidates.</p>
</li>
<li>
<p><code>Text.Readability.dale_chall/2</code> and <code>Text.Readability.spache/2</code> — the two classic word-list readability indices, backed by bundled easy-words lists in <code>priv/readability/</code> (Dale-Chall 2,949 words, Spache 1,063 words; both sourced from the MIT-licensed <code>py-readability-metrics</code> distribution of the public-domain originals). <code>statistics/2</code> now also returns <code>:difficult_words</code> and <code>:unfamiliar_words</code> counts.</p>
</li>
<li>
<p><code>Text.Hyphenation</code> bundles six additional language packs: <code>de-1996</code>, <code>fr</code>, <code>es</code>, <code>it</code>, <code>nl</code>, <code>pt</code>. All loaded at compile time with zero I/O, joining the existing <code>en-us</code> pack. Source: hyph-utf8 upstream; per-file licenses (MIT/X11/BSD/LPPL) are preserved in each <code>.tex</code> header.</p>
</li>
<li>
<p><code>Text.WordFreq</code> bundles six additional frequency tables at the same top-30,000 cap as English: <code>de</code>, <code>fr</code>, <code>es</code>, <code>it</code>, <code>nl</code>, <code>pt</code>. Source: Hermit Dave’s MIT-licensed <a href="https://github.com/hermitdave/FrequencyWords" rel="nofollow">FrequencyWords</a> OpenSubtitles 2018 corpus.</p>
</li>
<li>
<p><code>Text.Emoji.sentiment/1</code> and <code>Text.Emoji.text_sentiment/1</code> — per-emoji and aggregate sentiment scoring backed by the bundled Emoji Sentiment Ranking v1.0 (Kralj Novak et al., 2015;  ~750 emoji with negative/neutral/positive proportions and an aggregate score in <code>[-1.0, 1.0]</code>). Aggregate scoring is occurrence-weighted to match the original paper.</p>
</li>
</ul> 
	            </div>

	            <div class="base-line">
	                <div class="thread-counters">
	                    <span class="thread-count count-likes js-likers-trigger" title="Likes" data-post-id="388149" data-batch-url="/posts/batch_likers">
                        6
                      </span>
                      <!-- <span class="thread-count js-solved-indicator" title="Marked as solution"></span> -->
	                </div>
	                <div class="go-to-post">
	                  <a title="Go to post" alt="Go to post" href="https://forum.elixirforum.com/t/text-a-text-analysis-library/32363/20">Post #19</a>
	                </div>
	            </div>
              <div id="likers-container-388149" 
                   class="likers-container"
                   data-first-post="false"
                   data-batch-url="/posts/batch_likers">
                   <div class="likers-placeholder" 
                     data-likers-post-id="388149"
                     data-batch-url="/posts/batch_likers">
                  <div class="post-likers"></div>
                </div>
              </div>
	        </div>
			

    </div>

    <div class="triangle-top-right type-last-post cat-last-post" title="Last post!"></div>
  </section>
</div>
</template></turbo-stream><turbo-stream action="replace" target="load-more-container"><template><div id="load-more-container" class="load-more-container">
    <span class="all-loaded">— All posts loaded —</span>
</div></template></turbo-stream>