விக்சனரி tawiktionary https://ta.wiktionary.org/wiki/%E0%AE%AE%E0%AF%81%E0%AE%A4%E0%AE%B1%E0%AF%8D_%E0%AE%AA%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%AE%E0%AF%8D MediaWiki 1.47.0-wmf.24 case-sensitive ஊடகம் சிறப்பு பேச்சு பயனர் பயனர் பேச்சு விக்சனரி விக்சனரி பேச்சு படிமம் படிமப் பேச்சு மீடியாவிக்கி மீடியாவிக்கி பேச்சு வார்ப்புரு வார்ப்புரு பேச்சு உதவி உதவி பேச்சு பகுப்பு பகுப்பு பேச்சு TimedText TimedText talk Module Module talk Event Event talk விக்சனரி:ஆலமரத்தடி 4 1705 2004654 2004621 2026-10-26T14:01:23Z MediaWiki message delivery 6372 /* உங்கள் விக்கி விரைவில் வாசிக்க மட்டும் முடியும் நிலைக்கு செல்லும். */ புதிய பகுதி 2004654 wikitext text/x-wiki {{விக்சனரி:ஆலமரத்தடி/தலைப்பு}} == <span lang="en" dir="ltr">Tech News: 2026-36</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W36"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/36|Translations]] are available. '''Weekly highlight''' * A new format for the Community Wishlist is open for feedback. You can [[m:Special:MyLanguage/Community Wishlist/Community Wishlist 2027|read the proposed ideas on Meta]]. This new process plans to improve how wishes are triaged, voted on, and prioritized in a way that is transparent and balanced across project families and language editions. This consultation is open for two weeks. '''Updates for editors''' * The latest release of the Wikipedia Android app includes updates to the Saved feature, bringing the app’s saving experience closer to iOS and Web. The update redesigns the Saved tab with an “All articles” view, removes the default “Saved” reading list, renames reading lists to “Collections,” and modernizes the article-saving experience. [https://phabricator.wikimedia.org/T420788] * The [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|Reading Lists]] feature was enabled for all logged-in users on Bengali, Chinese, Czech and Vietnamese Wikipedias on August 25, after several months as a beta feature. Reading Lists will be available to all logged-in users on Arabic, French and Indonesian Wikipedias on September 1, followed by English Wikipedia on September 14, and all other Wikipedia wikis on September 28. * At the end of the month, some logged-out readers on Bengali, Czech, Persian, English, and Polish Wikipedias using the Minerva skin on mobile will see an [[mw:Special:MyLanguage/Readers/Reader_Growth/Minimal_Minerva|updated navigation bar]] in an [[w:A/B test|A/B test]]. The test will compare the current navigation bar with a new version designed to make it easier to find information more quickly. The goal is to determine whether these changes encourage readers to return more often. This experiment will not change the experience for logged-in readers and/or editors. * Editors who maintain redirects, templates, and categories used on redirect pages now have improved ways for finding and curating redirects. Previously, redirects pages could not be searched. Two new search keywords, <bdi lang="zxx" dir="ltr"><code><nowiki>onlyredirects:</nowiki></code></bdi> and <bdi lang="zxx" dir="ltr"><code><nowiki>withredirects:</nowiki></code></bdi>, now allow redirects to be searched directly and can be combined with existing keywords such as <bdi lang="zxx" dir="ltr"><code><nowiki>incategory:</nowiki></code></bdi>, <bdi lang="zxx" dir="ltr"><code><nowiki>intitle:</nowiki></code></bdi>, and <bdi lang="zxx" dir="ltr"><code><nowiki>insource:</nowiki></code></bdi>. [https://phabricator.wikimedia.org/T204089] * The ISBN lookup tools for generating citations were recently not working because of external service problems. Developers are working on solutions. [https://phabricator.wikimedia.org/T435179] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:30}} community-submitted {{PLURAL:30|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where searching for pages by category using <bdi lang="zxx" dir="ltr"><code><nowiki>deepcat</nowiki></code></bdi> could return no results or unrelated results has now been fixed. [https://phabricator.wikimedia.org/T414859] '''Updates for technical contributors''' * The domain of URLs for thumbnails is changing from upload.wikimedia.org to thumb.wikimedia.org. The old URLs will continue to work for the foreseeable future but MediaWiki will advertise the new domain instead. URLs to other types of media such as original files, videos and transcodes will still be served from upload.wikimedia.org. [https://phabricator.wikimedia.org/T427465] * The Wikimedia [https://www.mediawiki.org/w/index.php?api=wmf-math%2Fv1&title=Special%3ARestSandbox Math API] is now deprecated. These endpoints will be fully sunset by the end of September 2026. Developers who call these endpoints should transition to alternative math rendering solutions, such as the native [https://developer.mozilla.org/en-US/docs/Web/MathML MathML] or [https://www.mathjax.org/ MathJax]. Third-party MediaWiki installations that utilize the Math extension for formula rendering are required to upgrade to v1.43+ to avoid disruption of service. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.18|MediaWiki]] '''In depth''' * Read more about [[mw:Special:MyLanguage/Edit_check/TextMatch|TextMatch]] in a Diff post titled, [[diffblog:2026/08/28/custom-edit-suggestions-for-every-wiki-how-communities-are-shaping-suggestion-mode-with-textmatch/|Custom edit suggestions for every wiki: How communities are shaping Suggestion Mode with TextMatch]]. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/36|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W36"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 20:53, 31 ஆகத்து 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30990659 --> == <span lang="en" dir="ltr">Tech News: 2026-37</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W37"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/37|Translations]] are available. '''Updates for editors''' * [[mw:Special:MyLanguage/Help:Growth/Tools/Add_a_link|Add a Link]] has been upgraded for English Wikipedia users with improved detection of links that have different capitalization. This will help reduce ambiguous suggestions caused by differences in capitalization in article titles, including articles about cultural goods. A second phase of improvements is planned, which will also prepare the feature for release in other languages. [https://phabricator.wikimedia.org/T435526#12240876] [https://phabricator.wikimedia.org/T434259] * The [[mw:Special:MyLanguage/Article_guidance|Article guidance]] feature will be enabled by default for junior editors on Simple English and Turkish Wikipedia starting 10 September 2026 following [[mw:Special:MyLanguage/Article_guidance/Updates#Summary_of_experiment_result|an experiment]]. Junior editors with 0 to 99 edits will automatically see the feature when they click a red link or use the "[[tr:Vikipedi:Madde_sihirbazı|Madde oluşturto]]" option on Turkish Wikipedia to create a new article. The change is intended to help junior editors create higher-quality articles that meet each Wikipedia’s standards. [[phab:maniphest/query/z3tcTjmLMxk5/#R|Additional improvements]] will continue based on the experiment results and community feedback. * The [https://pageviews.wmcloud.org Pageviews Analysis] tool which allows users to compare pageviews across multiple pages, turns 10 years old this year and several new features have been added. They include [[toolforge:wikinav|WikiNav]] which provides insights into how readers of Wikipedia explore the content, editing stats in [https://pageviews.wmcloud.org/siteviews?range=latest-30&sites=en.wikipedia.org Siteviews], the ability to [https://pageviews.wmcloud.org/massviews?source=wikiproject&project=en.wikipedia.org lookup articles belonging to a WikiProject], and support for dark mode. [https://phabricator.wikimedia.org/T378549] * A [[mw:Special:MyLanguage/Readers/Reader_Growth/Minimal_Minerva|visually simplified Minerva]] navigation bar is being tested for logged-out readers using mobile web on the Bengali, Czech, English, Farsi, and Polish Wikipedias. The experiment aims to determine whether simplifying the navigation improves reader retention. The test will run from August 31 to September 28, and no action is required from users. * [[m:Special:MyLanguage/WMDE Technical Wishes|WMDE Technical Wishes]] is working on improving [[en:Wikipedia:VisualEditor/Named references|auto-generated reference names in VisualEditor]]. Editors will only notice a slight change starting this week. When adding automatic reference names the numbering will start at <bdi lang="zxx" dir="ltr"><code><nowiki>:1</nowiki></code></bdi> instead of <bdi lang="zxx" dir="ltr"><code><nowiki>:0</nowiki></code></bdi>. Read more on the [[m:WMDE Technical Wishes/References/VisualEditor automatic reference names|project page]]. [https://gerrit.wikimedia.org/r/c/VisualEditor/VisualEditor/+/1332704] * All wikis will be [[m:Special:MyLanguage/Tech/Server switch|read-only for a few minutes]] on September 23. This is planned at 14:00 UTC. More information will be published in Tech News and will also be posted on individual wikis in the coming weeks. [https://phabricator.wikimedia.org/T433363] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:25}} community-submitted {{PLURAL:25|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where [[mw:Special:MyLanguage/Parsoid|Parsoid]] could mishandle nested nowiki tags, causing content to be lost and displaying unwanted text has now been fixed. [https://phabricator.wikimedia.org/T435116] '''Updates for technical contributors''' * Interface administrators can configure gadgets from [[MediaWiki:Gadgets-definition]]. The definition format has been updated and no longer requires the <bdi lang="zxx" dir="ltr"><code><nowiki>ResourceLoader</nowiki></code></bdi> option, as gadgets are always loaded through <bdi lang="zxx" dir="ltr"><code><nowiki>ResourceLoader</nowiki></code></bdi>. This simplifies gadget configuration by removing an option that is no longer necessary, making gadget definitions easier for administrators. [https://phabricator.wikimedia.org/T298199] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.19|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/37|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W37"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 18:44, 7 செப்டெம்பர் 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31008437 --> == <span lang="en" dir="ltr">Tech News: 2026-38</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W38"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/38|Translations]] are available. '''Weekly highlight''' * The Future Audiences team has started a [[en:Special:MyLanguage/Wikipedia:Village pump (proposals)#New proposed experiment to let readers know about “Google preferred sources”|discussion on English Wikipedia]] about a proposed new experiment to show a temporary notice about the new [https://blog.google/products-and-platforms/products/search/original-high-quality-content-search/ Google Preferred Sources] feature to Wikipedia readers coming from Google. The experiment is to test whether this notice makes visitors come back to Wikipedia more often. Preferred Sources feature lets users choose websites they trust so Google can highlight content from those sources more prominently in Search and AI experiences. We are interested in testing this on other languages that Google supports and would welcome assistance with starting conversations on other wikis. If interested, please reach out to Future Audiences on the [[m:Special:MyLanguage/Future Audiences/Preferred sources|talk page]]. '''Updates for editors''' * The chat platform Discord is releasing a new self-service framework that websites can use to specify how their links should look when shared on Discord. The Future Audiences team is planning to start using this new framework to improve how Wikipedia links look when shared on Discord, giving better attribution and credit to contributors. The link appearance and behavior won't change in the first phase of this project as we want to first collect some baseline data to assess the impact of future changes. However, if community members who are active on Discord spot any issues, they can reach out on [[phab:tag/future-audiences/|Phabricator]] or [[m:Special:MyLanguage/Future Audiences|Metawiki]]. * The Reader Growth team will be running an experiment to test whether [[mw:Special:MyLanguage/Readers/Reader_Growth/Compact Lead|compacting article lead sections on mobiles]] with the addition of a "read more" button improves reader retention. The test will begin on September 17 on Arabic, Spanish, French, Indonesian, Italian, Japanese, Portuguese, Vietnamese, and Chinese Wikipedias and will run for four weeks. * All wikis will be [[m:Special:MyLanguage/Tech/Server switch|read-only for a few minutes]] on September 23. This is planned at 14:00 UTC. More information will be published in Tech News and will also be posted on individual wikis in the coming week. [https://phabricator.wikimedia.org/T433363] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:28}} community-submitted {{PLURAL:28|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where [https://wikistats.wmcloud.org/ Wikistats] for Wikipedias was returning an HTTP 500 error and could not be reached, has now been fixed. [https://phabricator.wikimedia.org/T435959] '''Updates for technical contributors''' * Developers who maintain a tool that queries the Wikimedia Commons links tables need to update their code to connect to the new x4 database cluster. The links tables have been moved from the s4 cluster to x4, and will no longer receive updates on s4. The page and redirect tables remain available on both clusters. A wiki replica for the x4 cluster will be set up afterwards. The change is being made because the s4 cluster has grown too large to operate efficiently. You can [[wikitech:Special:MyLanguage/News/2026 Commons links tables database split|read more]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.20|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/38|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W38"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 15:31, 14 செப்டெம்பர் 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31039826 --> == உங்கள் விக்கி விரைவில் வாசிக்க மட்டும் முடியும் நிலைக்கு செல்லும். == <section begin="server-switch"/><div class="plainlinks"> [[:m:Special:MyLanguage/Tech/Server switch|இச்செய்தியை பிறிதொரு மொழியில் படிக்கவும்]] • [https://meta.wikimedia.org/w/index.php?title=Special:Translate&group=page-Tech%2FServer+switch&language=&action=page&filter= {{int:please-translate}}] [[foundation:|விக்கிமீடியா நிறுவனம்]], தனது தரவு மையங்களுக்கிடையே போக்குவரத்தை மாற்றம் செய்கிறது. ஒரு பேரிடருக்கு பின்னும் விக்கிப்பீடியா மற்றும் இதர விக்கிமீடியா விக்கிகள் இணைப்பில் இருப்பதை இது உறுதிப்படுத்தும். எல்லா போக்குவரத்தும் '''{{#time:j xg|2026-09-23|ta}}''' அன்று மாற்றப்படும். சோதனை தொடங்கும் நேரம்: '''[https://zonestamp.toolforge.org/{{#time:U|2026-09-23T14:00|en}} {{#time:H:i e|2026-09-23T14:00}}]'''. நல்வாய்பற்ற விதத்தில், [[mw:Special:MyLanguage/Manual:What is MediaWiki?|மீடியாவிக்கியில்]] உள்ள சில குறைபாடுகள் காரணமாக, மாற்றம் செய்யப்படும் போது எல்லா தொகுப்பு செயல்களும் நிறுத்தப்பட வேண்டும். இந்த இடையூறுக்கு நாங்கள் வருந்துகிறோம். மேலும் இவற்றை வருங்காலத்தில் குறைப்பதற்கு முயல்கிறோம். இந்த செயல்பாடு நடப்பதற்கு 30 நிமிடங்களுக்கு முன்பு அனைத்து விக்கிகளிலும் ஒரு பேனர் காண்பிக்கப்படும். <span lang="en" dir="ltr" class="mw-content-ltr">This banner will remain visible until the end of the operation.</span> <span lang="en" dir="ltr" class="mw-content-ltr">You can contribute to the [https://meta.wikimedia.org/w/index.php?title=Special%3ATranslate&group=Centralnotice-tgroup-read_only_banner&task=view&language=&filter=&action=translate translation or proofreading] of this banner text.</span> '''நீங்கள் ஒரு குறுகிய காலத்திற்கு அனைத்து விக்கிகளையும் படிக்க முடியும், ஆனால் தொகுக்க முடியாது.''' * செவ்வாய், {{#time:l j xg Y|2026-09-23|ta}} நீங்கள் அதிக பட்சமாக ஒரு மணி நேரத்திற்கு தொகுக்க முடியாது. * இந்த நேரங்களில் நீங்கள் தொகுக்கவோ அல்லது சேமிக்கவோ முயன்றால், நீங்கள் ஒரு பிழை செய்தியை காண்பீர். இந்த நிமிடங்களில் எந்த தொகுப்பும் தொலையாது என நம்புகிறோம், ஆனால் எங்களால் அதை உறுதி செய்ய இயலாது. நீங்கள் அந்த பிழை செய்தியை கண்டால், தயவு செய்து எல்லாம் இயல்பு நிலைக்கு திரும்பும் வரை காத்திருக்கவும். அதன் பின் நீங்கள் உங்கள் தொகுப்பை சேமிக்க முடியும். இருப்பினும், முன்னெச்சரிக்கையாக உங்கள் மாற்றங்களை ஓர் பிரதி எடுத்து கொள்வதை நாங்கள் பரிந்துரைக்கிரோம். ''பிற விளைவுகள்'': * பின்புல வேலைகள் மந்தமாக இருக்கும். மேலும் அவற்றில் சில கைவிடப்படலாம். சிகப்பு இணைப்புகள் வழக்கமான வேகத்தில் புதுப்பிக்கப்படாமல் போகலாம். நீங்கள் வேறு எங்கேனும் இணைக்கப்பட்ட ஒரு கட்டுரையை உருவாக்கினால், அந்த இணைப்பு வழக்கத்தை விட அதிக நேரம் சிகப்பாகவே இருக்கும். சில நீண்ட நேரம் ஓடும் துணுக்குகள் நிறுத்தப்பட வேண்டி இருக்கும். * மற்ற வாரங்களைப் போலவே மூல பயன்கொள் நடக்கும் என்று நாங்கள் எதிர்பார்க்கிறோம். இருப்பினும், சில சூழ்நிலைகளில் செயல்பாட்டிற்கு பிறகு தேவைப்பட்டால் மூல முடக்கம் சரியான நேரத்தில் நடக்கலாம். * 90 நிமிடங்களுக்கு [[mw:Special:MyLanguage/GitLab|கிட்லாப் (GitLab)]]-ஐ பயன்படுத்த இயலாது. தேவைப்பட்டால் இந்த திட்டம் ஒத்திவைக்கப்படலாம். நீங்கள் [[wikitech:Switch_Datacenter|அட்டவணையை wikitech.wikimedia.org இல் படிக்கலாம்]]. எந்த மாற்றங்களும் அட்டவணையில் அறிவிக்கப்படும். '''தயவு செய்து இந்த தகவலை உங்கள் சமூகத்துடன் பகிர்ந்து கொள்ளுங்கள்.'''</div><section end="server-switch"/> [[m:user:Trizek (WMF)|Trizek (WMF)]] 12:58, 15 செப்டெம்பர் 2026 (UTC) <!-- Message sent by User:Trizek (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=30513874 --> == முதல் பன்னாட்டு சிறப்புரிமையுள்ள விக்கிமீடியர் மாநாடு == பிற விக்கிமீடிய மொழியினரும் கலந்து கொள்ளும் பன்னாட்டு நிகழ்வில், தமிழ் விக்கியரும் சிறப்பாக கலந்து கொண்ட பல விக்கிமீடிய நிகழ்வுகள் உள்ளன. அவற்றையும் நாம் ஆவணப்படுத்தினால், புதியவருக்கும் மிக பயனுள்ளதாக இருக்கும். அப்பக்கத்தினை நாம் எப்படி அமைக்கலாம். தொடக்கமாக, கீழுள்ள செய்தியைத் தருகிறேன். [[meta:Users_with_Extended_Rights_Conference_2026|பன்னாட்டு சிறப்புரிமையுள்ள விக்கிமீடியர் மாநாடு]] குறித்த மின்னஞ்சல், விண்ணப்பித்தவர்களுக்கு மட்டும் வந்துள்ளது. முழு உதவித்தொகை பெறுவதற்கான அனுமதி, எனக்குக் கிடைத்துள்ளது. நம்மில் வேறு யாருக்கும் கிடைத்துள்ளதா? இம்மாதம் 30-ஆம் தேதிக்குள், வந்த மின்னஞ்சலுக்கு, உரிய பதில் அனுப்ப வேண்டும். நம்மில் இம்மின்னஞ்சல் வந்தவர்கள் கலந்துரையாடினால் சிறப்பாக இருக்கும். சென்ற முறை பெர்லின், செருமனி செல்வதற்கு @[[பயனர்:Balajijagadesh|Balajijagadesh]] வழிகாட்டல்களும், விசா நடைமுறைகளை விளக்கி, உடன் வந்து, பெற்றுத்தந்த @[[பயனர்:Tshrinivasan|Tshrinivasan]] பேருதவியாக அமைந்தன. ஆவலுடன்.. [[User:Info-farmer|<font style="color:#318CE7">'''த<font color = "red">♥</font>உழவன்'''</font>]]<sup><big>[[User talk:Info-farmer|<font style="color:#FF8C00"> '''(உரை)''']] </font></big></sup> 01:25, 22 செப்டெம்பர் 2026 (UTC) == Your feedback wanted: Remove ''(Language link added:) and similar'' edits from Special:RecentChanges == ''(Apologies for posting in English. You can help by translating it)'' Hello, I’m Danny from [[:en:w:Wikimedia_Deutschland|Wikimedia Deutschland’s]] [[m:Wikidata_For_Wikimedia_Projects|Wikidata For Wikimedia Projects]] team.<br/> We have been investigating and examining how Wikidata’s use in the [[Special:RecentChanges]] and [[Special:Watchlist]] pages can be made to be more useful, or less ‘noisy’ (fewer edits that have little value to the reader). We are currently exploring to make a change in the volume and the type of Wikidata edits that appear in these pages, and '''we need your feedback''' to help us ensure this is a positive change for the Wikis and won’t disrupt your workflows.<br/> Language links are also known as sitelinks or interlanguage links and describe when a Wikidata item is connected to a Wiki, adding it to the language dropdown tool for other connected Wikis. [[File:Types of Lang Link changes.png|x350px|frame|A variety of "Language Link" edits as seen in the Recent Changes feed]] The request for feedback is whether you support the removal of these language link edits from the Recent Changes and Watchlist feed, or would you oppose such a change and if so, why? Please share you thoughts on the dedicated '''[[m:Talk:Wikidata_For_Wikimedia_Projects/Clearer_Wikidata_Edit_Summaries/Hide_Language_Link_edits|metawiki Talk page]]'''. Further information about this change: [[m:Wikidata_For_Wikimedia_Projects/Clearer_Wikidata_Edit_Summaries/Hide_Language_Link_edits|m:Wikidata_For_Wikimedia_Projects/Hide_Language_Link_edits]] Thank you for any participation or feedback you give, -- [[m:User:Danny_Benjafield_(WMDE)|User:Danny Benjafield (WMDE)]] 12:48, 25 செப்டெம்பர் 2026 (UTC) <!-- Message sent by User:Danny Benjafield (WMDE)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=User:Danny_Benjafield_(WMDE)/MassMessage_test_list&oldid=31081007 --> == <span lang="en" dir="ltr">Tech News: 2026-39</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W39"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/39|Translations]] are available. '''Weekly highlight''' * [[m:Special:MyLanguage/Tech/Server switch|All wikis will be read-only]] for a few minutes on Wednesday, 23 September 2026 at [https://zonestamp.toolforge.org/1790172000 14:00 UTC]. This is for the datacenter server switchover backup tests, [[wikitech:Special:MyLanguage/Deployments/Yearly calendar|which happen twice a year]]. During the switchover, all Wikimedia website traffic is shifted from one primary data center to the backup data center to test availability and prevent service disruption even in emergencies. [https://phabricator.wikimedia.org/T433363] '''Updates for editors''' * The Growth team tested a new post-edit notice designed to [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#4. Encourage Temporary Accounts to Register|encourage Temporary Account holders to register for a permanent account]]. The new notice replaced multiple dialogs and a simultaneous welcome notification with a single message highlighting the benefits of creating an account. The experiment increased permanent account creation from 1.94% to 3.66%, an 87% relative increase. The change will now be released to all wikis. [https://phabricator.wikimedia.org/T433551] * For Wikipedia editors using the [[Special:Preferences#mw-prefsection-betafeatures|"Suggestion mode" Beta Feature]], there is a [[Special:Preferences#mw-input-wpvisualeditor-editcheck-experimental|new opt-in user preference]] for showing "experimental" edit checks and suggestions. These types are listed at [[Special:EditChecks#experimental-checks|Special:EditChecks]], and are intended for early developer-testing and for gathering feedback from experienced users. Administrators can change the [[mw:Special:MyLanguage/Edit check/Configuration|configuration details]] for each suggestion as usual. Administrators can also [[mw:Special:MyLanguage/Help:Suggestion mode#Experimental Suggestions|use this feature]] to help test their community's ideas for locally created checks and suggestions. Experimental types will have a more distinct visual style later this week. [[mw:Talk:VisualEditor/Suggestion Mode|Feedback is welcome]]. * The [[m:CEE Technical Village Pump|CEE Technical Village Pump]] has been launched as a space for technical discussions and collaboration among Wikimedia communities in Central and Eastern Europe. * A new tool, [[mw:Special:MyLanguage/Language Onboarding and Development/Starter kit|Starter Kit]], is now available for new and small language Wikipedia communities. It brings together guided tasks, tools, and resources to help communities get started, monitor their progress, and collaborate with the broader Wikimedia community. Starter Kit is hosted on Wikimedia Toolforge and is designed for Wikipedias with fewer than 50,000 articles. Learn more about how Starter Kit works and how communities have been using it in [[diffblog:2026/09/18/introducing-starter-kit-for-new-and-small-language-wikipedias/|this Diff blog post]]. * The Reader Growth team is launching a retest of the [[mw:Special:MyLanguage/Readers/Reader Growth/Image Browsing|image carousel experiment]]. The retest will use three new versions of the design, updated based on community feedback. The team will assess results and determine with communities whether or not to proceed with the feature. The experiment will begin the week of September 28 and will run on Arabic, Bengali, Chinese, Czech, English, Farsi, French, German, Indonesian, Japanese, Polish, Portuguese, Spanish, Swedish, and Vietnamese Wikipedias. * After successful rollouts to Arabic, Bengali, Chinese, Czech, English, French, Indonesian, and Vietnamese Wikipedias, the [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|Reading Lists feature]] from the Reader Experience team will be available to all logged-in users on all Wikipedia wikis starting September 28. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:28}} community-submitted {{PLURAL:28|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where the mentor status script repeatedly updated mentors’ status even when nothing had changed, creating unnecessary Recent Changes entries, has now been fixed. [https://phabricator.wikimedia.org/T436659] '''Updates for technical contributors''' * The <bdi lang="zxx" dir="ltr"><code><nowiki>abusefilters</nowiki></code></bdi> list query API, used to retrieve specific details about active or historical abuse filters configured on a wiki, has been updated to support the <bdi lang="zxx" dir="ltr"><code><nowiki>formatversion=2</nowiki></code></bdi> configuration parameter, thereby leading to breaking changes to the API response. All users who maintain user-scripts or other code need to check if they use the <bdi lang="zxx" dir="ltr"><code><nowiki>abusefilters</nowiki></code></bdi> list query API, and respond to any breaking changes. [https://phabricator.wikimedia.org/T435828] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.21|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/39|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W39"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 11:56, 27 செப்டெம்பர் 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31085309 --> == <span lang="en" dir="ltr">Tech News: 2026-40</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W40"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/40|Translations]] are available. '''Updates for editors''' * Users of default skins on desktop and mobile web (Vector 2022 and Minerva respectively) will now be informed if the search suggestions displayed as they type into Search come from page redirects. Previously, users could get confused when the top search suggestions did not match exactly what they had typed. The new redirect notice clarifies that these suggestions are not random, but related to the search. The suggested pages may be redirects to the most relevant destination articles being searched for. [https://phabricator.wikimedia.org/T303013] * Logged-out Wikipedia users on mobile web will begin to see a bookmark button for saving articles to their [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|reading lists]] instead of the current Watchstar button. When clicked, the bookmark button prompts users to log in or create an account to save an article for later. An earlier experiment found that the bookmark icon was four times more likely than the Watchstar button to encourage logged-out users to create an account. This change is part of efforts to encourage more readers to become account holders and use features that help them save and return to content. [https://phabricator.wikimedia.org/T438769] * An experiment will begin on September 29 on Spanish, Arabic, English, and French Wikipedias to simplify the experience users encounter immediately after account creation. The test will explore whether removing friction from the current Welcome Survey helps new users understand what to do next more easily. The experiment will run through the end of October. [https://phabricator.wikimedia.org/T430058] * The [[m:Special:MyLanguage/Product and Technology Advisory Council|Product and Technology Advisory Council]] has put out a call for the community to suggest topics that it should consider and discuss with the Wikimedia Foundation. Editors and technical contributors are invited to add topics to [[m:Talk:Product and Technology Advisory Council|the council's talk page]]. * Later this week, it will be possible for communities to configure [[mw:Special:MyLanguage/Help:Edit check|Edit Checks and Suggestions]] so that they are [[mw:Special:MyLanguage/Edit check/Configuration#extraNamespaces|shown within additional namespaces]], such as a <bdi lang="zxx" dir="ltr"><code><nowiki>Draft:</nowiki></code></bdi> namespace. [[mw:Talk:VisualEditor/Suggestion Mode|Feedback is welcome]]. * A bug in the [[m:Special:GlobalWatchlist|Global Watchlist]] is causing the page to fail to load for users that have unseen changes on Wikimedia Commons. Developers are working on a fix. Until then, affected users can follow the workarounds described in the [[mw:Special:MyLanguage/Extension:GlobalWatchlist#GlobalWatchlist temporarily broken for users with Wikimedia Commons|relevant]] help section. * New magic words <bdi lang="zxx" dir="ltr"><code><nowiki>{{CATEGORYSORT:TIMESTAMP}}</nowiki></code></bdi> and <bdi lang="zxx" dir="ltr"><code><nowiki>{{CATEGORYSORT:RTIMESTAMP}}</nowiki></code></bdi> are now available for use. They are able to change the default sorting of categories to be based on timestamp of categorization. [https://phabricator.wikimedia.org/T433768] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:20}} community-submitted {{PLURAL:20|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the issue where [https://wikistats.wmcloud.org/ Wikistats] for Wikipedias was not reachable has now been fixed. [https://phabricator.wikimedia.org/T435959] '''Updates for technical contributors''' * The datacenter switchover, scheduled on September 23, has been postponed. [[diffblog:2025/03/12/hear-that-the-wikis-go-silent-twice-a-year/|The equinox exercise]] revealed capacity issues, preventing the switch of all services to the other datacenter. The process has been paused, prioritizing investigating that issue, to ensure that we continue to be able to serve our users reliably. A new exercise will be scheduled. Meanwhile, all traffic and edits continue to work as usual. * On September 23, a power loss briefly affected all wikis, creating intermittent issues on both reading and editing modes. This also affected Gerrit. This is unrelated to the datacenter switchover postponement. [https://www.wikimediastatus.net/incidents/9fm19t8qykk8] * [[mw:Special:MyLanguage/Help:Extension:Produnto|Produnto]] has been deployed to mediawiki.org and [[phab:T421436|some Indic language wikis]]. We want to hear your feedback on this experimental product. Produnto allows users to use Lua modules hosted in [[gitlab:repos/lua|Wikimedia's GitLab]]. It simplifies the sharing of Lua code between wikis. Instead of copying each individual Lua module from one wiki to another, users will be able to easily share packages across all wikis. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.22|MediaWiki]] '''Meetings and events''' * A [[m:Event:Cross-wiki Code Collaboration Workshop|Cross-wiki code collaboration workshop]] will be held online on October 2 at 12:30 UTC, bringing together South Asian technical contributors to learn about Produnto and begin piloting it on Hindi, Punjabi, Odia, Telugu, and Malayalam Wikipedias. Produnto is a package manager for deploying Lua modules hosted on GitLab to Wikimedia wikis, and was recently deployed on some pilot wikis. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/40|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W40"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 10:49, 28 செப்டெம்பர் 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31092225 --> == Wikidata Sitelink changes in Recent Changes and Watchlist == ''(Apologies for cross-posting in English, translating it will help spread the message)'' Editors on group 1 wikis, including Wikiquote, Wikibooks, Wikiversity, Wikivoyage, and Wikisource, will no longer see notifications on [[Special:RecentChanges|Recent Changes]] and [[Special:Watchlist|Watchlist]] when a link to ''another'' language version of a page is added, removed, or changed in its Wikidata item. This change will begin on '''October 5 at 07:00 UTC''' as a two-week trial to reduce high-volume, low-value notifications and make higher-value changes easier to identify. Editors who rely on these notifications should be aware of the change and can share feedback or concerns on the [[m:Wikidata For Wikimedia Projects/Clearer Wikidata Edit Summaries/Hide Language Link edits|project]] or [[m:Talk:Wikidata_For_Wikimedia_Projects/Clearer_Wikidata_Edit_Summaries/Hide_Language_Link_edits|talk page]].<br/>Please reach out if you have any questions, thank you! -- [[User:Danny_Benjafield_(WMDE)|Danny Benjafield (WMDE)]] ([[User_talk:Danny_Benjafield_(WMDE)|talk]]) sent via [[பயனர்:MediaWiki message delivery|MediaWiki message delivery]] ([[பயனர் பேச்சு:MediaWiki message delivery|பேச்சு]]) 12:42, 1 அக்டோபர் 2026 (UTC) <!-- Message sent by User:Danny Benjafield (WMDE)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=User:Danny_Benjafield_(WMDE)/MassMessage_test_list&oldid=31106967 --> == <span lang="en" dir="ltr">Tech News: 2026-41</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W41"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/41|Translations]] are available. '''Weekly highlight''' * The process for this year’s Community Wishlist [[m:Special:MyLanguage/Community Wishlist/Community Wishlist 2027#Updated Community Wishlist Process 2027/2028|has been published]], incorporating community feedback. The wish submission process will [[m:Special:MyLanguage/Community Wishlist/FAQ#What is the deadline for submitting wishes?|end on November 8]]. This year, [[m:Special:MyLanguage/Community Wishlist/Community Wishlist 2027/Working group|a working group (open for volunteering)]] that includes community members familiar with the technical aspect of our wikis alongside Wikimedia Foundation staff, will take care of the [[m:Special:MyLanguage/Community Wishlist/FAQ#What is triage?|triage process]]. The triage is expected to be completed by late November. Votes for wishes will start in January. '''Updates for editors''' * Editors using the [[mw:Special:MyLanguage/VisualEditor|visual editor]] will now see a warning when they paste text that is likely to have initially been copied from one of five external generative AI chatbots into Wikipedia. This feature is known as [[mw:Special:MyLanguage/Edit check/LLM Paste Check|LLM Paste Check]]. It is a type of [[mw:Special:MyLanguage/Edit check|Edit Check]] that is community configurable. The check looks for markers in the copied HTML and is not using content watermark detection. Edits will also [[mw:Special:MyLanguage/Edit check/LLM Paste Check#Edit tags|be tagged]] so that patrollers can review them more closely. The feature helps newcomers add content that respects Wikipedia’s policies and makes it easier for experienced editors to identify problematic edits. Communities with local policies about LLM usage can [[mw:Special:MyLanguage/Edit check/LLM Paste Check#Configuration|adjust the wording and policy that is linked]]. The team [[listarchive:list/translators-l@lists.wikimedia.org/message/EVHKWJ4IERZELMPIWNH3XUVUSQV5KOO4/|apologizes]] that this was rolled out without a delay for translations. * Editors can now easily [[mw:Special:MyLanguage/Wikimedia Apps/Team/Future of Editing on the Mobile Apps|access the web visual editor directly from the iOS and Android apps]]. When opening the editor, users will be given a choice between visual editing and source editing. If they choose visual editing, they will be sent to a mobile web window in their phone's browser. After completing their edit, a banner will give them the option to return to the app. This enables readers to make quick contributions while casually reading. * Users of the Wikipedia Android and iOS apps will now see a one-time account creation prompt after they have used the app for two consecutive weeks. This prompt explains the benefits of having an account. It is intended to help more casual readers become logged-in users who return to the apps regularly. [https://phabricator.wikimedia.org/T434847] * Editors on [[wmnoc:conf/dblists/group1.dblist|group1 wikis]], including Wikiquote, Wikibooks, Wikiversity, Wikivoyage, and Wikisource, will no longer see notifications on [[mw:Special:MyLanguage/Help:Recent changes|Recent Changes]] and [[mw:Special:MyLanguage/Help: Watching pages|Watchlist]] when a link to another language version of a page is added, removed, or changed in its Wikidata item. This change will begin on October 5 at 07:00 UTC as a two-week trial to reduce high-volume, low-value notifications and make important changes easier to identify. Editors who rely on these notifications can read more on the [[m:Wikidata For Wikimedia Projects/Clearer Wikidata Edit Summaries/Hide Language Link edits|project page]] and share feedback or concerns on the [[m:Talk:Wikidata_For_Wikimedia_Projects/Clearer_Wikidata_Edit_Summaries/Hide_Language_Link_edits|talk page]]. [https://phabricator.wikimedia.org/T439415] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:36}} community-submitted {{PLURAL:36|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where mentors who removed themselves from the mentorship program could still have mentees assigned to them and receive questions, has now been fixed. Mentees will now be reassigned when a mentor leaves the program. [https://phabricator.wikimedia.org/T418194] '''Updates for technical contributors''' * Wikimedia Brasil has published its [[m:Special:MyLanguage/Wikimedia Brasil/Arcabouço de Boas Práticas de Desenvolvimento|Development Best Practices Framework]] (ABPD). It is a guide for staff, contractors and volunteers building technical tools and projects. It covers the project lifecycle, security, accessibility, personal data protection and responsible AI use, and explains how movement-wide guidelines were adapted to a local context. They encourage other affiliates to reuse it and adapt it. Read more in the [[wmfblog:2026/09/26/wikimedia-brasils-development-best-practices-framework-technical-sustainability-as-an-organizational-responsibility|Diff post]]. * A new, canonical base URL for Wikimedia APIs will soon be introduced. The new canonical route follows a <bdi lang="zxx" dir="ltr"><code><nowiki>{projectName}/api/</nowiki></code></bdi> pattern and will act as an alternative path for current REST APIs (for example: <bdi lang="zxx" dir="ltr"><code><nowiki>{projectName}/w/rest.php/</nowiki></code></bdi>). Both versions will be callable for all existing APIs, but the new structure will be the preferred path for newly created API modules. This change will be non-breaking for most users. The existing pathways will remain available for all existing endpoint requests. However, some endpoints may return URLs following the new pattern in response bodies. Changes will be available on test wiki first, with the rollout expected to cover all wikis by the end of October. [https://phabricator.wikimedia.org/T433535] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.23|MediaWiki]]. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/41|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W41"/> </div> 18:52, 5 அக்டோபர் 2026 (UTC) <!-- Message sent by User:Trizek (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31098825 --> == <span lang="en" dir="ltr">Tech News: 2026-42</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W42"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/42|Translations]] are available. '''Weekly highlight''' * Starting October 13, wikis can change the default pattern for [[:en:Wikipedia:VisualEditor/Named references|auto-generated reference names in VisualEditor]] (currently <syntaxhighlight lang="wikitext" inline><ref name=":1" /></syntaxhighlight>). They will be able to use human-readable, content-based reference names that are more helpful to contributors using the source editor (e.g. <syntaxhighlight lang="wikitext" inline><ref name="Miller2026-1" /></syntaxhighlight>). VisualEditor reference name patterns can be defined using MediaWiki system messages, JSON, templates and modules ([[:mw:Special:MyLanguage/Help:Cite/VisualEditor reference names|more information]]). Admins can enable these changes via [[Special:CommunityConfiguration/Cite-VisualEditor-Autonames|community configuration]]. This is a [[:m:Special:MyLanguage/Community_Wishlist/W17|community wish]] implemented by [[:m:Special:MyLanguage/WMDE Technical Wishes|WMDE Technical Wishes]]. [https://phabricator.wikimedia.org/T436255] * [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|Sub-referencing]] is a new MediaWiki feature that allows editors to reuse references with different details. It will be deployed to English Wikipedia on October 14 and to remaining Wikimedia Foundation wikis by end of October (likely October 21). The [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing#deployment|FAQ]] lists possible actions to take on your wiki to support the deployment. This is a [[:m:WMDE Technical Wishes/Sub-referencing/History|long-standing community wish]] implemented by [[:m:Special:MyLanguage/WMDE Technical Wishes|WMDE Technical Wishes]]. [https://phabricator.wikimedia.org/T440243][https://phabricator.wikimedia.org/T440304] '''Updates for editors''' * [[mw:Special:MyLanguage/Moderator Tools|Moderator Tools]] will be removing the list view of the Watchlist and defaulting everyone to the changelog view. Previously, there were two versions of how the Watchlist is shown to mobile web users: the list view, where editors could see watched articles in a list format, and the changelog view, where editors could see changes to watched articles in a log format, controlled by a user's Advanced Mobile Control settings. This has created issues for code-base maintenance: two different displays means two sets of code to manage. By defaulting to the changelog view, we are simplifying the codebase and avoiding the likelihood of missing bugs. * The Wikipedia Android app is testing a way to listen to article introductions in the “For you” feed. A text-to-speech model reads the existing article text aloud without rewriting it or adding any content. The 30-day experiment will begin in the Wikipedia Beta app during the third week of September, then move to the regular Wikipedia Android app in October for a limited group of eligible users. A small portion of app users (those with English feeds and at least one topic interest) will have access to the feature. This experiment is part of broader efforts to give app readers options in how they consume content in order to deepen engagement and user retention. [[mw:Special:MyLanguage/Readers/Listening to Wikipedia/Phase 1|Learn more]] and [[mw:Talk:Readers/Listening to Wikipedia|share feedback]]. * [[mw:Special:MyLanguage/Help:Extension:CodeMirror|CodeMirror syntax highlighting]] was [[phab:T288161|enabled]] by default for all logged out and newly registered users on the English Wikipedia on September 14. With no signs of adverse effects, the same change is being proposed to be made globally across all wikis in a few weeks. If you have concerns or feedback, or if your wiki would like to opt out, [[mw:Special:MyLanguage/Help talk:Extension:CodeMirror#Enabling CodeMirror by default on all WMF wikis for newbies|please see the help page]]. [https://phabricator.wikimedia.org/T440077] * [[d:Wikidata:Usability_and_usefulness/Item_editing_experience/Mobile_editing_of_statements|Wikidata mobile statement editing]] of all datatypes will be officially launched on October 13, when it will be removed from Wikidata [[d:Special:Preferences#mw-prefsection-betafeatures|beta features]] and become available to all editors. Users can [[d:Wikidata_talk:Usability_and_usefulness/Item_editing_experience/Mobile_editing_of_statements|report bugs and share feedback]]. * Last week, language link changes were removed from [[Special:RecentChanges|Recent Changes]] and [[Special:Watchlist|Watchlist]] feeds on Wikivoyage, Wikisource, Wikiversity, Wikiquote and Wiktionary. As no adverse effects have been observed, the [[m:Wikidata_For_Wikimedia_Projects|Wikidata For Wikimedia Projects]] plans to roll out this change globally to all Wikipedias. This is scheduled for October 13 at 07:00 UTC, after which editors will no longer see when a link to ''another'' language version of a page is added, removed, or changed in its Wikidata item. Editors who rely on these notifications can read more on the [[m:Wikidata For Wikimedia Projects/Clearer Wikidata Edit Summaries/Hide Language Link edits|project page]] and share feedback or ask to opt out their wiki on the [[m:Talk:Wikidata_For_Wikimedia_Projects/Clearer_Wikidata_Edit_Summaries/Hide_Language_Link_edits|talk page]]. [https://phabricator.wikimedia.org/T440698] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:28}} community-submitted {{PLURAL:28|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where accidentally touching the graph while scrolling on the Pageviews tool on mobile could unintentionally zoom in has now been fixed. Click-and-drag zoom is now disabled on touch devices. [https://phabricator.wikimedia.org/T440075] '''Updates for technical contributors''' * There are three major changes coming to dumps: the legacy content dumps [https://lists.wikimedia.org/hyperkitty/list/xmldatadumps-l@lists.wikimedia.org/thread/E6D5EU4PMSTSOI2J7A46HJ3YW2W554CS/ deprecated in January 2026] will now be sunset, the replacement [https://wikitech.wikimedia.org/wiki/MediaWiki_Content_File_Exports MediaWiki Content File Exports] will be delayed to implement a “safety buffer”, and they are now also available as 7zip-compressed files. Users will need to switch the URL they download their dump from. For more information, timeline and migration instructions, see [https://wikitech.wikimedia.org/wiki/Legacy_content_dumps_sunset Legacy content dumps sunset]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.24|MediaWiki]] '''In depth''' * The Wikimedia Foundation discovered unauthorized activity by OpenAI agents on Wikimedia projects, including edits to wikis, attempts to exploit a public note-taking tool, and heavy automated traffic. [[diffblog:2026/10/05/openai-rogue-agent-activities-found-on-wikimedia-projects/|Read the blog post on Diff]] for more details about the activity and the Foundation’s response. '''Meetings and events''' * Save the date for the next Wikimedia Hackathon! We’ll be meeting in lovely Tirana, Albania for the [https://www.mediawiki.org/wiki/Wikimedia%20Hackathon%202027 2027 Wikimedia Hackathon] from April 9–11, 2027. Scholarship and staff sponsorship applications are currently being [https://pretix.eu/wikimedia/Hackathon-2027/ accepted through Pretix] and will close on October 30, 2026. General registration will open in January 2027. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/42|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W42"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 07:40, 13 அக்டோபர் 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31142468 --> == <span lang="en" dir="ltr">Tech News: 2026-43</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W43"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/43|Translations]] are available. '''Updates for editors''' * To measure the impact of incorporating some [[mw:Special:MyLanguage/Readers/Reader_Growth/Image_Browsing#November 2025: Ideas and feedback|community-requested features]] on logged-out reader retention, a test for [[mw:Special:MyLanguage/Readers/Reader_Growth/Image_Browsing|Image Carousel]] will be pushed to logged-out readers. With the Image Carousel feature, readers browse community-uploaded images, selected by editors as relevant to specific articles. The test starts on November 20. * An experimental notice will be pushed to 0.5% of logged-out mobile readers who come to English Wikipedia from Google, asking them to add Wikipedia as a [https://blog.google/products-and-platforms/products/search/preferred-sources/ Google Preferred Source]. The test is to see if this notice increases reader retention and encourages people who use Google Search to visit Wikipedia more often. It will run from October 15–29 and will be assessed for its impact on retention and referrals. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:26}} community-submitted {{PLURAL:26|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where [[quarry:|Quarry]] returned an “Access denied” error when users tried to run SQL queries, preventing queries from running normally, has now been fixed. [https://phabricator.wikimedia.org/T440966] '''Updates for technical contributors''' * [[File:Cog-icon-grey.svg|12px|link=|class=skin-invert|Advanced item]] Due to a long-standing bug, applications that use the [[mw:Special:MyLanguage/OAuth/For_Developers|OAuth 2]] client credentials flow to act on their owners’ behalf are treated as if they are logged out. This will be fixed for new applications registered on or after Monday, October 19. Some affected applications need to re-register. [https://phabricator.wikimedia.org/T420297] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.48/wmf.1|MediaWiki]] '''Meetings and events''' * A proposal to hold the Wikimedia Hackathon every three years, with regional hackathons in between, [[mw:Wikimedia Hackathon/Community Proposal 2026|has been shared]]. It explores making the event more accessible to technical communities in different regions. Share your feedback on the proposal’s [[mw:Talk:Wikimedia Hackathon/Community Proposal 2026|talk page]] before November 6. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/43|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W43"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 14:38, 19 அக்டோபர் 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31178499 --> == உங்கள் விக்கி விரைவில் வாசிக்க மட்டும் முடியும் நிலைக்கு செல்லும். == <section begin="server-switch"/><div class="plainlinks"> [[:m:Special:MyLanguage/Tech/Server switch|இச்செய்தியை பிறிதொரு மொழியில் படிக்கவும்]] • [https://meta.wikimedia.org/w/index.php?title=Special:Translate&group=page-Tech%2FServer+switch&language=&action=page&filter= {{int:please-translate}}] [[foundation:|விக்கிமீடியா நிறுவனம்]], தனது தரவு மையங்களுக்கிடையே போக்குவரத்தை மாற்றம் செய்கிறது. ஒரு பேரிடருக்கு பின்னும் விக்கிப்பீடியா மற்றும் இதர விக்கிமீடியா விக்கிகள் இணைப்பில் இருப்பதை இது உறுதிப்படுத்தும். எல்லா போக்குவரத்தும் '''{{#time:j xg|2026-10-28|ta}}''' அன்று மாற்றப்படும். சோதனை தொடங்கும் நேரம்: '''[https://zonestamp.toolforge.org/{{#time:U|2026-10-28T15:00|en}} {{#time:H:i e|2026-10-28T15:00}}]'''. நல்வாய்பற்ற விதத்தில், [[mw:Special:MyLanguage/Manual:What is MediaWiki?|மீடியாவிக்கியில்]] உள்ள சில குறைபாடுகள் காரணமாக, மாற்றம் செய்யப்படும் போது எல்லா தொகுப்பு செயல்களும் நிறுத்தப்பட வேண்டும். இந்த இடையூறுக்கு நாங்கள் வருந்துகிறோம். மேலும் இவற்றை வருங்காலத்தில் குறைப்பதற்கு முயல்கிறோம். இந்த செயல்பாடு நடப்பதற்கு 30 நிமிடங்களுக்கு முன்பு அனைத்து விக்கிகளிலும் ஒரு பேனர் காண்பிக்கப்படும். <span lang="en" dir="ltr" class="mw-content-ltr">This banner will remain visible until the end of the operation.</span> <span lang="en" dir="ltr" class="mw-content-ltr">You can contribute to the [https://meta.wikimedia.org/w/index.php?title=Special%3ATranslate&group=Centralnotice-tgroup-read_only_banner&task=view&language=&filter=&action=translate translation or proofreading] of this banner text.</span> '''நீங்கள் ஒரு குறுகிய காலத்திற்கு அனைத்து விக்கிகளையும் படிக்க முடியும், ஆனால் தொகுக்க முடியாது.''' * செவ்வாய், {{#time:l j xg Y|2026-10-28|ta}} நீங்கள் அதிக பட்சமாக ஒரு மணி நேரத்திற்கு தொகுக்க முடியாது. * இந்த நேரங்களில் நீங்கள் தொகுக்கவோ அல்லது சேமிக்கவோ முயன்றால், நீங்கள் ஒரு பிழை செய்தியை காண்பீர். இந்த நிமிடங்களில் எந்த தொகுப்பும் தொலையாது என நம்புகிறோம், ஆனால் எங்களால் அதை உறுதி செய்ய இயலாது. நீங்கள் அந்த பிழை செய்தியை கண்டால், தயவு செய்து எல்லாம் இயல்பு நிலைக்கு திரும்பும் வரை காத்திருக்கவும். அதன் பின் நீங்கள் உங்கள் தொகுப்பை சேமிக்க முடியும். இருப்பினும், முன்னெச்சரிக்கையாக உங்கள் மாற்றங்களை ஓர் பிரதி எடுத்து கொள்வதை நாங்கள் பரிந்துரைக்கிரோம். ''பிற விளைவுகள்'': * பின்புல வேலைகள் மந்தமாக இருக்கும். மேலும் அவற்றில் சில கைவிடப்படலாம். சிகப்பு இணைப்புகள் வழக்கமான வேகத்தில் புதுப்பிக்கப்படாமல் போகலாம். நீங்கள் வேறு எங்கேனும் இணைக்கப்பட்ட ஒரு கட்டுரையை உருவாக்கினால், அந்த இணைப்பு வழக்கத்தை விட அதிக நேரம் சிகப்பாகவே இருக்கும். சில நீண்ட நேரம் ஓடும் துணுக்குகள் நிறுத்தப்பட வேண்டி இருக்கும். * மற்ற வாரங்களைப் போலவே மூல பயன்கொள் நடக்கும் என்று நாங்கள் எதிர்பார்க்கிறோம். இருப்பினும், சில சூழ்நிலைகளில் செயல்பாட்டிற்கு பிறகு தேவைப்பட்டால் மூல முடக்கம் சரியான நேரத்தில் நடக்கலாம். * 90 நிமிடங்களுக்கு [[mw:Special:MyLanguage/GitLab|கிட்லாப் (GitLab)]]-ஐ பயன்படுத்த இயலாது. தேவைப்பட்டால் இந்த திட்டம் ஒத்திவைக்கப்படலாம். நீங்கள் [[wikitech:Switch_Datacenter|அட்டவணையை wikitech.wikimedia.org இல் படிக்கலாம்]]. எந்த மாற்றங்களும் அட்டவணையில் அறிவிக்கப்படும். '''தயவு செய்து இந்த தகவலை உங்கள் சமூகத்துடன் பகிர்ந்து கொள்ளுங்கள்.'''</div><section end="server-switch"/> [[user:Trizek_(WMF)|Trizek_(WMF)]] 14:01, 26 அக்டோபர் 2026 (UTC) <!-- Message sent by User:Trizek (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=30513874 --> k11c65zjb8p54pufbwqfnk064trrgc3 2004658 2004654 2026-10-26T18:56:51Z MediaWiki message delivery 6372 /* Tech News: 2026-44 */ புதிய பகுதி 2004658 wikitext text/x-wiki {{விக்சனரி:ஆலமரத்தடி/தலைப்பு}} == <span lang="en" dir="ltr">Tech News: 2026-36</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W36"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/36|Translations]] are available. '''Weekly highlight''' * A new format for the Community Wishlist is open for feedback. You can [[m:Special:MyLanguage/Community Wishlist/Community Wishlist 2027|read the proposed ideas on Meta]]. This new process plans to improve how wishes are triaged, voted on, and prioritized in a way that is transparent and balanced across project families and language editions. This consultation is open for two weeks. '''Updates for editors''' * The latest release of the Wikipedia Android app includes updates to the Saved feature, bringing the app’s saving experience closer to iOS and Web. The update redesigns the Saved tab with an “All articles” view, removes the default “Saved” reading list, renames reading lists to “Collections,” and modernizes the article-saving experience. [https://phabricator.wikimedia.org/T420788] * The [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|Reading Lists]] feature was enabled for all logged-in users on Bengali, Chinese, Czech and Vietnamese Wikipedias on August 25, after several months as a beta feature. Reading Lists will be available to all logged-in users on Arabic, French and Indonesian Wikipedias on September 1, followed by English Wikipedia on September 14, and all other Wikipedia wikis on September 28. * At the end of the month, some logged-out readers on Bengali, Czech, Persian, English, and Polish Wikipedias using the Minerva skin on mobile will see an [[mw:Special:MyLanguage/Readers/Reader_Growth/Minimal_Minerva|updated navigation bar]] in an [[w:A/B test|A/B test]]. The test will compare the current navigation bar with a new version designed to make it easier to find information more quickly. The goal is to determine whether these changes encourage readers to return more often. This experiment will not change the experience for logged-in readers and/or editors. * Editors who maintain redirects, templates, and categories used on redirect pages now have improved ways for finding and curating redirects. Previously, redirects pages could not be searched. Two new search keywords, <bdi lang="zxx" dir="ltr"><code><nowiki>onlyredirects:</nowiki></code></bdi> and <bdi lang="zxx" dir="ltr"><code><nowiki>withredirects:</nowiki></code></bdi>, now allow redirects to be searched directly and can be combined with existing keywords such as <bdi lang="zxx" dir="ltr"><code><nowiki>incategory:</nowiki></code></bdi>, <bdi lang="zxx" dir="ltr"><code><nowiki>intitle:</nowiki></code></bdi>, and <bdi lang="zxx" dir="ltr"><code><nowiki>insource:</nowiki></code></bdi>. [https://phabricator.wikimedia.org/T204089] * The ISBN lookup tools for generating citations were recently not working because of external service problems. Developers are working on solutions. [https://phabricator.wikimedia.org/T435179] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:30}} community-submitted {{PLURAL:30|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where searching for pages by category using <bdi lang="zxx" dir="ltr"><code><nowiki>deepcat</nowiki></code></bdi> could return no results or unrelated results has now been fixed. [https://phabricator.wikimedia.org/T414859] '''Updates for technical contributors''' * The domain of URLs for thumbnails is changing from upload.wikimedia.org to thumb.wikimedia.org. The old URLs will continue to work for the foreseeable future but MediaWiki will advertise the new domain instead. URLs to other types of media such as original files, videos and transcodes will still be served from upload.wikimedia.org. [https://phabricator.wikimedia.org/T427465] * The Wikimedia [https://www.mediawiki.org/w/index.php?api=wmf-math%2Fv1&title=Special%3ARestSandbox Math API] is now deprecated. These endpoints will be fully sunset by the end of September 2026. Developers who call these endpoints should transition to alternative math rendering solutions, such as the native [https://developer.mozilla.org/en-US/docs/Web/MathML MathML] or [https://www.mathjax.org/ MathJax]. Third-party MediaWiki installations that utilize the Math extension for formula rendering are required to upgrade to v1.43+ to avoid disruption of service. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.18|MediaWiki]] '''In depth''' * Read more about [[mw:Special:MyLanguage/Edit_check/TextMatch|TextMatch]] in a Diff post titled, [[diffblog:2026/08/28/custom-edit-suggestions-for-every-wiki-how-communities-are-shaping-suggestion-mode-with-textmatch/|Custom edit suggestions for every wiki: How communities are shaping Suggestion Mode with TextMatch]]. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/36|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W36"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 20:53, 31 ஆகத்து 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=30990659 --> == <span lang="en" dir="ltr">Tech News: 2026-37</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W37"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/37|Translations]] are available. '''Updates for editors''' * [[mw:Special:MyLanguage/Help:Growth/Tools/Add_a_link|Add a Link]] has been upgraded for English Wikipedia users with improved detection of links that have different capitalization. This will help reduce ambiguous suggestions caused by differences in capitalization in article titles, including articles about cultural goods. A second phase of improvements is planned, which will also prepare the feature for release in other languages. [https://phabricator.wikimedia.org/T435526#12240876] [https://phabricator.wikimedia.org/T434259] * The [[mw:Special:MyLanguage/Article_guidance|Article guidance]] feature will be enabled by default for junior editors on Simple English and Turkish Wikipedia starting 10 September 2026 following [[mw:Special:MyLanguage/Article_guidance/Updates#Summary_of_experiment_result|an experiment]]. Junior editors with 0 to 99 edits will automatically see the feature when they click a red link or use the "[[tr:Vikipedi:Madde_sihirbazı|Madde oluşturto]]" option on Turkish Wikipedia to create a new article. The change is intended to help junior editors create higher-quality articles that meet each Wikipedia’s standards. [[phab:maniphest/query/z3tcTjmLMxk5/#R|Additional improvements]] will continue based on the experiment results and community feedback. * The [https://pageviews.wmcloud.org Pageviews Analysis] tool which allows users to compare pageviews across multiple pages, turns 10 years old this year and several new features have been added. They include [[toolforge:wikinav|WikiNav]] which provides insights into how readers of Wikipedia explore the content, editing stats in [https://pageviews.wmcloud.org/siteviews?range=latest-30&sites=en.wikipedia.org Siteviews], the ability to [https://pageviews.wmcloud.org/massviews?source=wikiproject&project=en.wikipedia.org lookup articles belonging to a WikiProject], and support for dark mode. [https://phabricator.wikimedia.org/T378549] * A [[mw:Special:MyLanguage/Readers/Reader_Growth/Minimal_Minerva|visually simplified Minerva]] navigation bar is being tested for logged-out readers using mobile web on the Bengali, Czech, English, Farsi, and Polish Wikipedias. The experiment aims to determine whether simplifying the navigation improves reader retention. The test will run from August 31 to September 28, and no action is required from users. * [[m:Special:MyLanguage/WMDE Technical Wishes|WMDE Technical Wishes]] is working on improving [[en:Wikipedia:VisualEditor/Named references|auto-generated reference names in VisualEditor]]. Editors will only notice a slight change starting this week. When adding automatic reference names the numbering will start at <bdi lang="zxx" dir="ltr"><code><nowiki>:1</nowiki></code></bdi> instead of <bdi lang="zxx" dir="ltr"><code><nowiki>:0</nowiki></code></bdi>. Read more on the [[m:WMDE Technical Wishes/References/VisualEditor automatic reference names|project page]]. [https://gerrit.wikimedia.org/r/c/VisualEditor/VisualEditor/+/1332704] * All wikis will be [[m:Special:MyLanguage/Tech/Server switch|read-only for a few minutes]] on September 23. This is planned at 14:00 UTC. More information will be published in Tech News and will also be posted on individual wikis in the coming weeks. [https://phabricator.wikimedia.org/T433363] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:25}} community-submitted {{PLURAL:25|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where [[mw:Special:MyLanguage/Parsoid|Parsoid]] could mishandle nested nowiki tags, causing content to be lost and displaying unwanted text has now been fixed. [https://phabricator.wikimedia.org/T435116] '''Updates for technical contributors''' * Interface administrators can configure gadgets from [[MediaWiki:Gadgets-definition]]. The definition format has been updated and no longer requires the <bdi lang="zxx" dir="ltr"><code><nowiki>ResourceLoader</nowiki></code></bdi> option, as gadgets are always loaded through <bdi lang="zxx" dir="ltr"><code><nowiki>ResourceLoader</nowiki></code></bdi>. This simplifies gadget configuration by removing an option that is no longer necessary, making gadget definitions easier for administrators. [https://phabricator.wikimedia.org/T298199] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.19|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/37|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W37"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 18:44, 7 செப்டெம்பர் 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31008437 --> == <span lang="en" dir="ltr">Tech News: 2026-38</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W38"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/38|Translations]] are available. '''Weekly highlight''' * The Future Audiences team has started a [[en:Special:MyLanguage/Wikipedia:Village pump (proposals)#New proposed experiment to let readers know about “Google preferred sources”|discussion on English Wikipedia]] about a proposed new experiment to show a temporary notice about the new [https://blog.google/products-and-platforms/products/search/original-high-quality-content-search/ Google Preferred Sources] feature to Wikipedia readers coming from Google. The experiment is to test whether this notice makes visitors come back to Wikipedia more often. Preferred Sources feature lets users choose websites they trust so Google can highlight content from those sources more prominently in Search and AI experiences. We are interested in testing this on other languages that Google supports and would welcome assistance with starting conversations on other wikis. If interested, please reach out to Future Audiences on the [[m:Special:MyLanguage/Future Audiences/Preferred sources|talk page]]. '''Updates for editors''' * The chat platform Discord is releasing a new self-service framework that websites can use to specify how their links should look when shared on Discord. The Future Audiences team is planning to start using this new framework to improve how Wikipedia links look when shared on Discord, giving better attribution and credit to contributors. The link appearance and behavior won't change in the first phase of this project as we want to first collect some baseline data to assess the impact of future changes. However, if community members who are active on Discord spot any issues, they can reach out on [[phab:tag/future-audiences/|Phabricator]] or [[m:Special:MyLanguage/Future Audiences|Metawiki]]. * The Reader Growth team will be running an experiment to test whether [[mw:Special:MyLanguage/Readers/Reader_Growth/Compact Lead|compacting article lead sections on mobiles]] with the addition of a "read more" button improves reader retention. The test will begin on September 17 on Arabic, Spanish, French, Indonesian, Italian, Japanese, Portuguese, Vietnamese, and Chinese Wikipedias and will run for four weeks. * All wikis will be [[m:Special:MyLanguage/Tech/Server switch|read-only for a few minutes]] on September 23. This is planned at 14:00 UTC. More information will be published in Tech News and will also be posted on individual wikis in the coming week. [https://phabricator.wikimedia.org/T433363] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:28}} community-submitted {{PLURAL:28|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where [https://wikistats.wmcloud.org/ Wikistats] for Wikipedias was returning an HTTP 500 error and could not be reached, has now been fixed. [https://phabricator.wikimedia.org/T435959] '''Updates for technical contributors''' * Developers who maintain a tool that queries the Wikimedia Commons links tables need to update their code to connect to the new x4 database cluster. The links tables have been moved from the s4 cluster to x4, and will no longer receive updates on s4. The page and redirect tables remain available on both clusters. A wiki replica for the x4 cluster will be set up afterwards. The change is being made because the s4 cluster has grown too large to operate efficiently. You can [[wikitech:Special:MyLanguage/News/2026 Commons links tables database split|read more]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.20|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/38|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W38"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 15:31, 14 செப்டெம்பர் 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31039826 --> == உங்கள் விக்கி விரைவில் வாசிக்க மட்டும் முடியும் நிலைக்கு செல்லும். == <section begin="server-switch"/><div class="plainlinks"> [[:m:Special:MyLanguage/Tech/Server switch|இச்செய்தியை பிறிதொரு மொழியில் படிக்கவும்]] • [https://meta.wikimedia.org/w/index.php?title=Special:Translate&group=page-Tech%2FServer+switch&language=&action=page&filter= {{int:please-translate}}] [[foundation:|விக்கிமீடியா நிறுவனம்]], தனது தரவு மையங்களுக்கிடையே போக்குவரத்தை மாற்றம் செய்கிறது. ஒரு பேரிடருக்கு பின்னும் விக்கிப்பீடியா மற்றும் இதர விக்கிமீடியா விக்கிகள் இணைப்பில் இருப்பதை இது உறுதிப்படுத்தும். எல்லா போக்குவரத்தும் '''{{#time:j xg|2026-09-23|ta}}''' அன்று மாற்றப்படும். சோதனை தொடங்கும் நேரம்: '''[https://zonestamp.toolforge.org/{{#time:U|2026-09-23T14:00|en}} {{#time:H:i e|2026-09-23T14:00}}]'''. நல்வாய்பற்ற விதத்தில், [[mw:Special:MyLanguage/Manual:What is MediaWiki?|மீடியாவிக்கியில்]] உள்ள சில குறைபாடுகள் காரணமாக, மாற்றம் செய்யப்படும் போது எல்லா தொகுப்பு செயல்களும் நிறுத்தப்பட வேண்டும். இந்த இடையூறுக்கு நாங்கள் வருந்துகிறோம். மேலும் இவற்றை வருங்காலத்தில் குறைப்பதற்கு முயல்கிறோம். இந்த செயல்பாடு நடப்பதற்கு 30 நிமிடங்களுக்கு முன்பு அனைத்து விக்கிகளிலும் ஒரு பேனர் காண்பிக்கப்படும். <span lang="en" dir="ltr" class="mw-content-ltr">This banner will remain visible until the end of the operation.</span> <span lang="en" dir="ltr" class="mw-content-ltr">You can contribute to the [https://meta.wikimedia.org/w/index.php?title=Special%3ATranslate&group=Centralnotice-tgroup-read_only_banner&task=view&language=&filter=&action=translate translation or proofreading] of this banner text.</span> '''நீங்கள் ஒரு குறுகிய காலத்திற்கு அனைத்து விக்கிகளையும் படிக்க முடியும், ஆனால் தொகுக்க முடியாது.''' * செவ்வாய், {{#time:l j xg Y|2026-09-23|ta}} நீங்கள் அதிக பட்சமாக ஒரு மணி நேரத்திற்கு தொகுக்க முடியாது. * இந்த நேரங்களில் நீங்கள் தொகுக்கவோ அல்லது சேமிக்கவோ முயன்றால், நீங்கள் ஒரு பிழை செய்தியை காண்பீர். இந்த நிமிடங்களில் எந்த தொகுப்பும் தொலையாது என நம்புகிறோம், ஆனால் எங்களால் அதை உறுதி செய்ய இயலாது. நீங்கள் அந்த பிழை செய்தியை கண்டால், தயவு செய்து எல்லாம் இயல்பு நிலைக்கு திரும்பும் வரை காத்திருக்கவும். அதன் பின் நீங்கள் உங்கள் தொகுப்பை சேமிக்க முடியும். இருப்பினும், முன்னெச்சரிக்கையாக உங்கள் மாற்றங்களை ஓர் பிரதி எடுத்து கொள்வதை நாங்கள் பரிந்துரைக்கிரோம். ''பிற விளைவுகள்'': * பின்புல வேலைகள் மந்தமாக இருக்கும். மேலும் அவற்றில் சில கைவிடப்படலாம். சிகப்பு இணைப்புகள் வழக்கமான வேகத்தில் புதுப்பிக்கப்படாமல் போகலாம். நீங்கள் வேறு எங்கேனும் இணைக்கப்பட்ட ஒரு கட்டுரையை உருவாக்கினால், அந்த இணைப்பு வழக்கத்தை விட அதிக நேரம் சிகப்பாகவே இருக்கும். சில நீண்ட நேரம் ஓடும் துணுக்குகள் நிறுத்தப்பட வேண்டி இருக்கும். * மற்ற வாரங்களைப் போலவே மூல பயன்கொள் நடக்கும் என்று நாங்கள் எதிர்பார்க்கிறோம். இருப்பினும், சில சூழ்நிலைகளில் செயல்பாட்டிற்கு பிறகு தேவைப்பட்டால் மூல முடக்கம் சரியான நேரத்தில் நடக்கலாம். * 90 நிமிடங்களுக்கு [[mw:Special:MyLanguage/GitLab|கிட்லாப் (GitLab)]]-ஐ பயன்படுத்த இயலாது. தேவைப்பட்டால் இந்த திட்டம் ஒத்திவைக்கப்படலாம். நீங்கள் [[wikitech:Switch_Datacenter|அட்டவணையை wikitech.wikimedia.org இல் படிக்கலாம்]]. எந்த மாற்றங்களும் அட்டவணையில் அறிவிக்கப்படும். '''தயவு செய்து இந்த தகவலை உங்கள் சமூகத்துடன் பகிர்ந்து கொள்ளுங்கள்.'''</div><section end="server-switch"/> [[m:user:Trizek (WMF)|Trizek (WMF)]] 12:58, 15 செப்டெம்பர் 2026 (UTC) <!-- Message sent by User:Trizek (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=30513874 --> == முதல் பன்னாட்டு சிறப்புரிமையுள்ள விக்கிமீடியர் மாநாடு == பிற விக்கிமீடிய மொழியினரும் கலந்து கொள்ளும் பன்னாட்டு நிகழ்வில், தமிழ் விக்கியரும் சிறப்பாக கலந்து கொண்ட பல விக்கிமீடிய நிகழ்வுகள் உள்ளன. அவற்றையும் நாம் ஆவணப்படுத்தினால், புதியவருக்கும் மிக பயனுள்ளதாக இருக்கும். அப்பக்கத்தினை நாம் எப்படி அமைக்கலாம். தொடக்கமாக, கீழுள்ள செய்தியைத் தருகிறேன். [[meta:Users_with_Extended_Rights_Conference_2026|பன்னாட்டு சிறப்புரிமையுள்ள விக்கிமீடியர் மாநாடு]] குறித்த மின்னஞ்சல், விண்ணப்பித்தவர்களுக்கு மட்டும் வந்துள்ளது. முழு உதவித்தொகை பெறுவதற்கான அனுமதி, எனக்குக் கிடைத்துள்ளது. நம்மில் வேறு யாருக்கும் கிடைத்துள்ளதா? இம்மாதம் 30-ஆம் தேதிக்குள், வந்த மின்னஞ்சலுக்கு, உரிய பதில் அனுப்ப வேண்டும். நம்மில் இம்மின்னஞ்சல் வந்தவர்கள் கலந்துரையாடினால் சிறப்பாக இருக்கும். சென்ற முறை பெர்லின், செருமனி செல்வதற்கு @[[பயனர்:Balajijagadesh|Balajijagadesh]] வழிகாட்டல்களும், விசா நடைமுறைகளை விளக்கி, உடன் வந்து, பெற்றுத்தந்த @[[பயனர்:Tshrinivasan|Tshrinivasan]] பேருதவியாக அமைந்தன. ஆவலுடன்.. [[User:Info-farmer|<font style="color:#318CE7">'''த<font color = "red">♥</font>உழவன்'''</font>]]<sup><big>[[User talk:Info-farmer|<font style="color:#FF8C00"> '''(உரை)''']] </font></big></sup> 01:25, 22 செப்டெம்பர் 2026 (UTC) == Your feedback wanted: Remove ''(Language link added:) and similar'' edits from Special:RecentChanges == ''(Apologies for posting in English. You can help by translating it)'' Hello, I’m Danny from [[:en:w:Wikimedia_Deutschland|Wikimedia Deutschland’s]] [[m:Wikidata_For_Wikimedia_Projects|Wikidata For Wikimedia Projects]] team.<br/> We have been investigating and examining how Wikidata’s use in the [[Special:RecentChanges]] and [[Special:Watchlist]] pages can be made to be more useful, or less ‘noisy’ (fewer edits that have little value to the reader). We are currently exploring to make a change in the volume and the type of Wikidata edits that appear in these pages, and '''we need your feedback''' to help us ensure this is a positive change for the Wikis and won’t disrupt your workflows.<br/> Language links are also known as sitelinks or interlanguage links and describe when a Wikidata item is connected to a Wiki, adding it to the language dropdown tool for other connected Wikis. [[File:Types of Lang Link changes.png|x350px|frame|A variety of "Language Link" edits as seen in the Recent Changes feed]] The request for feedback is whether you support the removal of these language link edits from the Recent Changes and Watchlist feed, or would you oppose such a change and if so, why? Please share you thoughts on the dedicated '''[[m:Talk:Wikidata_For_Wikimedia_Projects/Clearer_Wikidata_Edit_Summaries/Hide_Language_Link_edits|metawiki Talk page]]'''. Further information about this change: [[m:Wikidata_For_Wikimedia_Projects/Clearer_Wikidata_Edit_Summaries/Hide_Language_Link_edits|m:Wikidata_For_Wikimedia_Projects/Hide_Language_Link_edits]] Thank you for any participation or feedback you give, -- [[m:User:Danny_Benjafield_(WMDE)|User:Danny Benjafield (WMDE)]] 12:48, 25 செப்டெம்பர் 2026 (UTC) <!-- Message sent by User:Danny Benjafield (WMDE)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=User:Danny_Benjafield_(WMDE)/MassMessage_test_list&oldid=31081007 --> == <span lang="en" dir="ltr">Tech News: 2026-39</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W39"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/39|Translations]] are available. '''Weekly highlight''' * [[m:Special:MyLanguage/Tech/Server switch|All wikis will be read-only]] for a few minutes on Wednesday, 23 September 2026 at [https://zonestamp.toolforge.org/1790172000 14:00 UTC]. This is for the datacenter server switchover backup tests, [[wikitech:Special:MyLanguage/Deployments/Yearly calendar|which happen twice a year]]. During the switchover, all Wikimedia website traffic is shifted from one primary data center to the backup data center to test availability and prevent service disruption even in emergencies. [https://phabricator.wikimedia.org/T433363] '''Updates for editors''' * The Growth team tested a new post-edit notice designed to [[mw:Special:MyLanguage/Contributors/Account Creation Experiments#4. Encourage Temporary Accounts to Register|encourage Temporary Account holders to register for a permanent account]]. The new notice replaced multiple dialogs and a simultaneous welcome notification with a single message highlighting the benefits of creating an account. The experiment increased permanent account creation from 1.94% to 3.66%, an 87% relative increase. The change will now be released to all wikis. [https://phabricator.wikimedia.org/T433551] * For Wikipedia editors using the [[Special:Preferences#mw-prefsection-betafeatures|"Suggestion mode" Beta Feature]], there is a [[Special:Preferences#mw-input-wpvisualeditor-editcheck-experimental|new opt-in user preference]] for showing "experimental" edit checks and suggestions. These types are listed at [[Special:EditChecks#experimental-checks|Special:EditChecks]], and are intended for early developer-testing and for gathering feedback from experienced users. Administrators can change the [[mw:Special:MyLanguage/Edit check/Configuration|configuration details]] for each suggestion as usual. Administrators can also [[mw:Special:MyLanguage/Help:Suggestion mode#Experimental Suggestions|use this feature]] to help test their community's ideas for locally created checks and suggestions. Experimental types will have a more distinct visual style later this week. [[mw:Talk:VisualEditor/Suggestion Mode|Feedback is welcome]]. * The [[m:CEE Technical Village Pump|CEE Technical Village Pump]] has been launched as a space for technical discussions and collaboration among Wikimedia communities in Central and Eastern Europe. * A new tool, [[mw:Special:MyLanguage/Language Onboarding and Development/Starter kit|Starter Kit]], is now available for new and small language Wikipedia communities. It brings together guided tasks, tools, and resources to help communities get started, monitor their progress, and collaborate with the broader Wikimedia community. Starter Kit is hosted on Wikimedia Toolforge and is designed for Wikipedias with fewer than 50,000 articles. Learn more about how Starter Kit works and how communities have been using it in [[diffblog:2026/09/18/introducing-starter-kit-for-new-and-small-language-wikipedias/|this Diff blog post]]. * The Reader Growth team is launching a retest of the [[mw:Special:MyLanguage/Readers/Reader Growth/Image Browsing|image carousel experiment]]. The retest will use three new versions of the design, updated based on community feedback. The team will assess results and determine with communities whether or not to proceed with the feature. The experiment will begin the week of September 28 and will run on Arabic, Bengali, Chinese, Czech, English, Farsi, French, German, Indonesian, Japanese, Polish, Portuguese, Spanish, Swedish, and Vietnamese Wikipedias. * After successful rollouts to Arabic, Bengali, Chinese, Czech, English, French, Indonesian, and Vietnamese Wikipedias, the [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|Reading Lists feature]] from the Reader Experience team will be available to all logged-in users on all Wikipedia wikis starting September 28. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:28}} community-submitted {{PLURAL:28|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where the mentor status script repeatedly updated mentors’ status even when nothing had changed, creating unnecessary Recent Changes entries, has now been fixed. [https://phabricator.wikimedia.org/T436659] '''Updates for technical contributors''' * The <bdi lang="zxx" dir="ltr"><code><nowiki>abusefilters</nowiki></code></bdi> list query API, used to retrieve specific details about active or historical abuse filters configured on a wiki, has been updated to support the <bdi lang="zxx" dir="ltr"><code><nowiki>formatversion=2</nowiki></code></bdi> configuration parameter, thereby leading to breaking changes to the API response. All users who maintain user-scripts or other code need to check if they use the <bdi lang="zxx" dir="ltr"><code><nowiki>abusefilters</nowiki></code></bdi> list query API, and respond to any breaking changes. [https://phabricator.wikimedia.org/T435828] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.21|MediaWiki]] '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/39|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W39"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 11:56, 27 செப்டெம்பர் 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31085309 --> == <span lang="en" dir="ltr">Tech News: 2026-40</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W40"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/40|Translations]] are available. '''Updates for editors''' * Users of default skins on desktop and mobile web (Vector 2022 and Minerva respectively) will now be informed if the search suggestions displayed as they type into Search come from page redirects. Previously, users could get confused when the top search suggestions did not match exactly what they had typed. The new redirect notice clarifies that these suggestions are not random, but related to the search. The suggested pages may be redirects to the most relevant destination articles being searched for. [https://phabricator.wikimedia.org/T303013] * Logged-out Wikipedia users on mobile web will begin to see a bookmark button for saving articles to their [[mw:Special:MyLanguage/Readers/Reader Experience/Reading lists|reading lists]] instead of the current Watchstar button. When clicked, the bookmark button prompts users to log in or create an account to save an article for later. An earlier experiment found that the bookmark icon was four times more likely than the Watchstar button to encourage logged-out users to create an account. This change is part of efforts to encourage more readers to become account holders and use features that help them save and return to content. [https://phabricator.wikimedia.org/T438769] * An experiment will begin on September 29 on Spanish, Arabic, English, and French Wikipedias to simplify the experience users encounter immediately after account creation. The test will explore whether removing friction from the current Welcome Survey helps new users understand what to do next more easily. The experiment will run through the end of October. [https://phabricator.wikimedia.org/T430058] * The [[m:Special:MyLanguage/Product and Technology Advisory Council|Product and Technology Advisory Council]] has put out a call for the community to suggest topics that it should consider and discuss with the Wikimedia Foundation. Editors and technical contributors are invited to add topics to [[m:Talk:Product and Technology Advisory Council|the council's talk page]]. * Later this week, it will be possible for communities to configure [[mw:Special:MyLanguage/Help:Edit check|Edit Checks and Suggestions]] so that they are [[mw:Special:MyLanguage/Edit check/Configuration#extraNamespaces|shown within additional namespaces]], such as a <bdi lang="zxx" dir="ltr"><code><nowiki>Draft:</nowiki></code></bdi> namespace. [[mw:Talk:VisualEditor/Suggestion Mode|Feedback is welcome]]. * A bug in the [[m:Special:GlobalWatchlist|Global Watchlist]] is causing the page to fail to load for users that have unseen changes on Wikimedia Commons. Developers are working on a fix. Until then, affected users can follow the workarounds described in the [[mw:Special:MyLanguage/Extension:GlobalWatchlist#GlobalWatchlist temporarily broken for users with Wikimedia Commons|relevant]] help section. * New magic words <bdi lang="zxx" dir="ltr"><code><nowiki>{{CATEGORYSORT:TIMESTAMP}}</nowiki></code></bdi> and <bdi lang="zxx" dir="ltr"><code><nowiki>{{CATEGORYSORT:RTIMESTAMP}}</nowiki></code></bdi> are now available for use. They are able to change the default sorting of categories to be based on timestamp of categorization. [https://phabricator.wikimedia.org/T433768] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:20}} community-submitted {{PLURAL:20|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the issue where [https://wikistats.wmcloud.org/ Wikistats] for Wikipedias was not reachable has now been fixed. [https://phabricator.wikimedia.org/T435959] '''Updates for technical contributors''' * The datacenter switchover, scheduled on September 23, has been postponed. [[diffblog:2025/03/12/hear-that-the-wikis-go-silent-twice-a-year/|The equinox exercise]] revealed capacity issues, preventing the switch of all services to the other datacenter. The process has been paused, prioritizing investigating that issue, to ensure that we continue to be able to serve our users reliably. A new exercise will be scheduled. Meanwhile, all traffic and edits continue to work as usual. * On September 23, a power loss briefly affected all wikis, creating intermittent issues on both reading and editing modes. This also affected Gerrit. This is unrelated to the datacenter switchover postponement. [https://www.wikimediastatus.net/incidents/9fm19t8qykk8] * [[mw:Special:MyLanguage/Help:Extension:Produnto|Produnto]] has been deployed to mediawiki.org and [[phab:T421436|some Indic language wikis]]. We want to hear your feedback on this experimental product. Produnto allows users to use Lua modules hosted in [[gitlab:repos/lua|Wikimedia's GitLab]]. It simplifies the sharing of Lua code between wikis. Instead of copying each individual Lua module from one wiki to another, users will be able to easily share packages across all wikis. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.22|MediaWiki]] '''Meetings and events''' * A [[m:Event:Cross-wiki Code Collaboration Workshop|Cross-wiki code collaboration workshop]] will be held online on October 2 at 12:30 UTC, bringing together South Asian technical contributors to learn about Produnto and begin piloting it on Hindi, Punjabi, Odia, Telugu, and Malayalam Wikipedias. Produnto is a package manager for deploying Lua modules hosted on GitLab to Wikimedia wikis, and was recently deployed on some pilot wikis. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/40|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W40"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 10:49, 28 செப்டெம்பர் 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31092225 --> == Wikidata Sitelink changes in Recent Changes and Watchlist == ''(Apologies for cross-posting in English, translating it will help spread the message)'' Editors on group 1 wikis, including Wikiquote, Wikibooks, Wikiversity, Wikivoyage, and Wikisource, will no longer see notifications on [[Special:RecentChanges|Recent Changes]] and [[Special:Watchlist|Watchlist]] when a link to ''another'' language version of a page is added, removed, or changed in its Wikidata item. This change will begin on '''October 5 at 07:00 UTC''' as a two-week trial to reduce high-volume, low-value notifications and make higher-value changes easier to identify. Editors who rely on these notifications should be aware of the change and can share feedback or concerns on the [[m:Wikidata For Wikimedia Projects/Clearer Wikidata Edit Summaries/Hide Language Link edits|project]] or [[m:Talk:Wikidata_For_Wikimedia_Projects/Clearer_Wikidata_Edit_Summaries/Hide_Language_Link_edits|talk page]].<br/>Please reach out if you have any questions, thank you! -- [[User:Danny_Benjafield_(WMDE)|Danny Benjafield (WMDE)]] ([[User_talk:Danny_Benjafield_(WMDE)|talk]]) sent via [[பயனர்:MediaWiki message delivery|MediaWiki message delivery]] ([[பயனர் பேச்சு:MediaWiki message delivery|பேச்சு]]) 12:42, 1 அக்டோபர் 2026 (UTC) <!-- Message sent by User:Danny Benjafield (WMDE)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=User:Danny_Benjafield_(WMDE)/MassMessage_test_list&oldid=31106967 --> == <span lang="en" dir="ltr">Tech News: 2026-41</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W41"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/41|Translations]] are available. '''Weekly highlight''' * The process for this year’s Community Wishlist [[m:Special:MyLanguage/Community Wishlist/Community Wishlist 2027#Updated Community Wishlist Process 2027/2028|has been published]], incorporating community feedback. The wish submission process will [[m:Special:MyLanguage/Community Wishlist/FAQ#What is the deadline for submitting wishes?|end on November 8]]. This year, [[m:Special:MyLanguage/Community Wishlist/Community Wishlist 2027/Working group|a working group (open for volunteering)]] that includes community members familiar with the technical aspect of our wikis alongside Wikimedia Foundation staff, will take care of the [[m:Special:MyLanguage/Community Wishlist/FAQ#What is triage?|triage process]]. The triage is expected to be completed by late November. Votes for wishes will start in January. '''Updates for editors''' * Editors using the [[mw:Special:MyLanguage/VisualEditor|visual editor]] will now see a warning when they paste text that is likely to have initially been copied from one of five external generative AI chatbots into Wikipedia. This feature is known as [[mw:Special:MyLanguage/Edit check/LLM Paste Check|LLM Paste Check]]. It is a type of [[mw:Special:MyLanguage/Edit check|Edit Check]] that is community configurable. The check looks for markers in the copied HTML and is not using content watermark detection. Edits will also [[mw:Special:MyLanguage/Edit check/LLM Paste Check#Edit tags|be tagged]] so that patrollers can review them more closely. The feature helps newcomers add content that respects Wikipedia’s policies and makes it easier for experienced editors to identify problematic edits. Communities with local policies about LLM usage can [[mw:Special:MyLanguage/Edit check/LLM Paste Check#Configuration|adjust the wording and policy that is linked]]. The team [[listarchive:list/translators-l@lists.wikimedia.org/message/EVHKWJ4IERZELMPIWNH3XUVUSQV5KOO4/|apologizes]] that this was rolled out without a delay for translations. * Editors can now easily [[mw:Special:MyLanguage/Wikimedia Apps/Team/Future of Editing on the Mobile Apps|access the web visual editor directly from the iOS and Android apps]]. When opening the editor, users will be given a choice between visual editing and source editing. If they choose visual editing, they will be sent to a mobile web window in their phone's browser. After completing their edit, a banner will give them the option to return to the app. This enables readers to make quick contributions while casually reading. * Users of the Wikipedia Android and iOS apps will now see a one-time account creation prompt after they have used the app for two consecutive weeks. This prompt explains the benefits of having an account. It is intended to help more casual readers become logged-in users who return to the apps regularly. [https://phabricator.wikimedia.org/T434847] * Editors on [[wmnoc:conf/dblists/group1.dblist|group1 wikis]], including Wikiquote, Wikibooks, Wikiversity, Wikivoyage, and Wikisource, will no longer see notifications on [[mw:Special:MyLanguage/Help:Recent changes|Recent Changes]] and [[mw:Special:MyLanguage/Help: Watching pages|Watchlist]] when a link to another language version of a page is added, removed, or changed in its Wikidata item. This change will begin on October 5 at 07:00 UTC as a two-week trial to reduce high-volume, low-value notifications and make important changes easier to identify. Editors who rely on these notifications can read more on the [[m:Wikidata For Wikimedia Projects/Clearer Wikidata Edit Summaries/Hide Language Link edits|project page]] and share feedback or concerns on the [[m:Talk:Wikidata_For_Wikimedia_Projects/Clearer_Wikidata_Edit_Summaries/Hide_Language_Link_edits|talk page]]. [https://phabricator.wikimedia.org/T439415] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:36}} community-submitted {{PLURAL:36|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where mentors who removed themselves from the mentorship program could still have mentees assigned to them and receive questions, has now been fixed. Mentees will now be reassigned when a mentor leaves the program. [https://phabricator.wikimedia.org/T418194] '''Updates for technical contributors''' * Wikimedia Brasil has published its [[m:Special:MyLanguage/Wikimedia Brasil/Arcabouço de Boas Práticas de Desenvolvimento|Development Best Practices Framework]] (ABPD). It is a guide for staff, contractors and volunteers building technical tools and projects. It covers the project lifecycle, security, accessibility, personal data protection and responsible AI use, and explains how movement-wide guidelines were adapted to a local context. They encourage other affiliates to reuse it and adapt it. Read more in the [[wmfblog:2026/09/26/wikimedia-brasils-development-best-practices-framework-technical-sustainability-as-an-organizational-responsibility|Diff post]]. * A new, canonical base URL for Wikimedia APIs will soon be introduced. The new canonical route follows a <bdi lang="zxx" dir="ltr"><code><nowiki>{projectName}/api/</nowiki></code></bdi> pattern and will act as an alternative path for current REST APIs (for example: <bdi lang="zxx" dir="ltr"><code><nowiki>{projectName}/w/rest.php/</nowiki></code></bdi>). Both versions will be callable for all existing APIs, but the new structure will be the preferred path for newly created API modules. This change will be non-breaking for most users. The existing pathways will remain available for all existing endpoint requests. However, some endpoints may return URLs following the new pattern in response bodies. Changes will be available on test wiki first, with the rollout expected to cover all wikis by the end of October. [https://phabricator.wikimedia.org/T433535] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.23|MediaWiki]]. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/41|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W41"/> </div> 18:52, 5 அக்டோபர் 2026 (UTC) <!-- Message sent by User:Trizek (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31098825 --> == <span lang="en" dir="ltr">Tech News: 2026-42</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W42"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/42|Translations]] are available. '''Weekly highlight''' * Starting October 13, wikis can change the default pattern for [[:en:Wikipedia:VisualEditor/Named references|auto-generated reference names in VisualEditor]] (currently <syntaxhighlight lang="wikitext" inline><ref name=":1" /></syntaxhighlight>). They will be able to use human-readable, content-based reference names that are more helpful to contributors using the source editor (e.g. <syntaxhighlight lang="wikitext" inline><ref name="Miller2026-1" /></syntaxhighlight>). VisualEditor reference name patterns can be defined using MediaWiki system messages, JSON, templates and modules ([[:mw:Special:MyLanguage/Help:Cite/VisualEditor reference names|more information]]). Admins can enable these changes via [[Special:CommunityConfiguration/Cite-VisualEditor-Autonames|community configuration]]. This is a [[:m:Special:MyLanguage/Community_Wishlist/W17|community wish]] implemented by [[:m:Special:MyLanguage/WMDE Technical Wishes|WMDE Technical Wishes]]. [https://phabricator.wikimedia.org/T436255] * [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing|Sub-referencing]] is a new MediaWiki feature that allows editors to reuse references with different details. It will be deployed to English Wikipedia on October 14 and to remaining Wikimedia Foundation wikis by end of October (likely October 21). The [[m:Special:MyLanguage/WMDE Technical Wishes/Sub-referencing#deployment|FAQ]] lists possible actions to take on your wiki to support the deployment. This is a [[:m:WMDE Technical Wishes/Sub-referencing/History|long-standing community wish]] implemented by [[:m:Special:MyLanguage/WMDE Technical Wishes|WMDE Technical Wishes]]. [https://phabricator.wikimedia.org/T440243][https://phabricator.wikimedia.org/T440304] '''Updates for editors''' * [[mw:Special:MyLanguage/Moderator Tools|Moderator Tools]] will be removing the list view of the Watchlist and defaulting everyone to the changelog view. Previously, there were two versions of how the Watchlist is shown to mobile web users: the list view, where editors could see watched articles in a list format, and the changelog view, where editors could see changes to watched articles in a log format, controlled by a user's Advanced Mobile Control settings. This has created issues for code-base maintenance: two different displays means two sets of code to manage. By defaulting to the changelog view, we are simplifying the codebase and avoiding the likelihood of missing bugs. * The Wikipedia Android app is testing a way to listen to article introductions in the “For you” feed. A text-to-speech model reads the existing article text aloud without rewriting it or adding any content. The 30-day experiment will begin in the Wikipedia Beta app during the third week of September, then move to the regular Wikipedia Android app in October for a limited group of eligible users. A small portion of app users (those with English feeds and at least one topic interest) will have access to the feature. This experiment is part of broader efforts to give app readers options in how they consume content in order to deepen engagement and user retention. [[mw:Special:MyLanguage/Readers/Listening to Wikipedia/Phase 1|Learn more]] and [[mw:Talk:Readers/Listening to Wikipedia|share feedback]]. * [[mw:Special:MyLanguage/Help:Extension:CodeMirror|CodeMirror syntax highlighting]] was [[phab:T288161|enabled]] by default for all logged out and newly registered users on the English Wikipedia on September 14. With no signs of adverse effects, the same change is being proposed to be made globally across all wikis in a few weeks. If you have concerns or feedback, or if your wiki would like to opt out, [[mw:Special:MyLanguage/Help talk:Extension:CodeMirror#Enabling CodeMirror by default on all WMF wikis for newbies|please see the help page]]. [https://phabricator.wikimedia.org/T440077] * [[d:Wikidata:Usability_and_usefulness/Item_editing_experience/Mobile_editing_of_statements|Wikidata mobile statement editing]] of all datatypes will be officially launched on October 13, when it will be removed from Wikidata [[d:Special:Preferences#mw-prefsection-betafeatures|beta features]] and become available to all editors. Users can [[d:Wikidata_talk:Usability_and_usefulness/Item_editing_experience/Mobile_editing_of_statements|report bugs and share feedback]]. * Last week, language link changes were removed from [[Special:RecentChanges|Recent Changes]] and [[Special:Watchlist|Watchlist]] feeds on Wikivoyage, Wikisource, Wikiversity, Wikiquote and Wiktionary. As no adverse effects have been observed, the [[m:Wikidata_For_Wikimedia_Projects|Wikidata For Wikimedia Projects]] plans to roll out this change globally to all Wikipedias. This is scheduled for October 13 at 07:00 UTC, after which editors will no longer see when a link to ''another'' language version of a page is added, removed, or changed in its Wikidata item. Editors who rely on these notifications can read more on the [[m:Wikidata For Wikimedia Projects/Clearer Wikidata Edit Summaries/Hide Language Link edits|project page]] and share feedback or ask to opt out their wiki on the [[m:Talk:Wikidata_For_Wikimedia_Projects/Clearer_Wikidata_Edit_Summaries/Hide_Language_Link_edits|talk page]]. [https://phabricator.wikimedia.org/T440698] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:28}} community-submitted {{PLURAL:28|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where accidentally touching the graph while scrolling on the Pageviews tool on mobile could unintentionally zoom in has now been fixed. Click-and-drag zoom is now disabled on touch devices. [https://phabricator.wikimedia.org/T440075] '''Updates for technical contributors''' * There are three major changes coming to dumps: the legacy content dumps [https://lists.wikimedia.org/hyperkitty/list/xmldatadumps-l@lists.wikimedia.org/thread/E6D5EU4PMSTSOI2J7A46HJ3YW2W554CS/ deprecated in January 2026] will now be sunset, the replacement [https://wikitech.wikimedia.org/wiki/MediaWiki_Content_File_Exports MediaWiki Content File Exports] will be delayed to implement a “safety buffer”, and they are now also available as 7zip-compressed files. Users will need to switch the URL they download their dump from. For more information, timeline and migration instructions, see [https://wikitech.wikimedia.org/wiki/Legacy_content_dumps_sunset Legacy content dumps sunset]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.47/wmf.24|MediaWiki]] '''In depth''' * The Wikimedia Foundation discovered unauthorized activity by OpenAI agents on Wikimedia projects, including edits to wikis, attempts to exploit a public note-taking tool, and heavy automated traffic. [[diffblog:2026/10/05/openai-rogue-agent-activities-found-on-wikimedia-projects/|Read the blog post on Diff]] for more details about the activity and the Foundation’s response. '''Meetings and events''' * Save the date for the next Wikimedia Hackathon! We’ll be meeting in lovely Tirana, Albania for the [https://www.mediawiki.org/wiki/Wikimedia%20Hackathon%202027 2027 Wikimedia Hackathon] from April 9–11, 2027. Scholarship and staff sponsorship applications are currently being [https://pretix.eu/wikimedia/Hackathon-2027/ accepted through Pretix] and will close on October 30, 2026. General registration will open in January 2027. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/42|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W42"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 07:40, 13 அக்டோபர் 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31142468 --> == <span lang="en" dir="ltr">Tech News: 2026-43</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W43"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/43|Translations]] are available. '''Updates for editors''' * To measure the impact of incorporating some [[mw:Special:MyLanguage/Readers/Reader_Growth/Image_Browsing#November 2025: Ideas and feedback|community-requested features]] on logged-out reader retention, a test for [[mw:Special:MyLanguage/Readers/Reader_Growth/Image_Browsing|Image Carousel]] will be pushed to logged-out readers. With the Image Carousel feature, readers browse community-uploaded images, selected by editors as relevant to specific articles. The test starts on November 20. * An experimental notice will be pushed to 0.5% of logged-out mobile readers who come to English Wikipedia from Google, asking them to add Wikipedia as a [https://blog.google/products-and-platforms/products/search/preferred-sources/ Google Preferred Source]. The test is to see if this notice increases reader retention and encourages people who use Google Search to visit Wikipedia more often. It will run from October 15–29 and will be assessed for its impact on retention and referrals. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:26}} community-submitted {{PLURAL:26|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, an issue where [[quarry:|Quarry]] returned an “Access denied” error when users tried to run SQL queries, preventing queries from running normally, has now been fixed. [https://phabricator.wikimedia.org/T440966] '''Updates for technical contributors''' * [[File:Cog-icon-grey.svg|12px|link=|class=skin-invert|Advanced item]] Due to a long-standing bug, applications that use the [[mw:Special:MyLanguage/OAuth/For_Developers|OAuth 2]] client credentials flow to act on their owners’ behalf are treated as if they are logged out. This will be fixed for new applications registered on or after Monday, October 19. Some affected applications need to re-register. [https://phabricator.wikimedia.org/T420297] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.48/wmf.1|MediaWiki]] '''Meetings and events''' * A proposal to hold the Wikimedia Hackathon every three years, with regional hackathons in between, [[mw:Wikimedia Hackathon/Community Proposal 2026|has been shared]]. It explores making the event more accessible to technical communities in different regions. Share your feedback on the proposal’s [[mw:Talk:Wikimedia Hackathon/Community Proposal 2026|talk page]] before November 6. '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/43|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W43"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 14:38, 19 அக்டோபர் 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31178499 --> == உங்கள் விக்கி விரைவில் வாசிக்க மட்டும் முடியும் நிலைக்கு செல்லும். == <section begin="server-switch"/><div class="plainlinks"> [[:m:Special:MyLanguage/Tech/Server switch|இச்செய்தியை பிறிதொரு மொழியில் படிக்கவும்]] • [https://meta.wikimedia.org/w/index.php?title=Special:Translate&group=page-Tech%2FServer+switch&language=&action=page&filter= {{int:please-translate}}] [[foundation:|விக்கிமீடியா நிறுவனம்]], தனது தரவு மையங்களுக்கிடையே போக்குவரத்தை மாற்றம் செய்கிறது. ஒரு பேரிடருக்கு பின்னும் விக்கிப்பீடியா மற்றும் இதர விக்கிமீடியா விக்கிகள் இணைப்பில் இருப்பதை இது உறுதிப்படுத்தும். எல்லா போக்குவரத்தும் '''{{#time:j xg|2026-10-28|ta}}''' அன்று மாற்றப்படும். சோதனை தொடங்கும் நேரம்: '''[https://zonestamp.toolforge.org/{{#time:U|2026-10-28T15:00|en}} {{#time:H:i e|2026-10-28T15:00}}]'''. நல்வாய்பற்ற விதத்தில், [[mw:Special:MyLanguage/Manual:What is MediaWiki?|மீடியாவிக்கியில்]] உள்ள சில குறைபாடுகள் காரணமாக, மாற்றம் செய்யப்படும் போது எல்லா தொகுப்பு செயல்களும் நிறுத்தப்பட வேண்டும். இந்த இடையூறுக்கு நாங்கள் வருந்துகிறோம். மேலும் இவற்றை வருங்காலத்தில் குறைப்பதற்கு முயல்கிறோம். இந்த செயல்பாடு நடப்பதற்கு 30 நிமிடங்களுக்கு முன்பு அனைத்து விக்கிகளிலும் ஒரு பேனர் காண்பிக்கப்படும். <span lang="en" dir="ltr" class="mw-content-ltr">This banner will remain visible until the end of the operation.</span> <span lang="en" dir="ltr" class="mw-content-ltr">You can contribute to the [https://meta.wikimedia.org/w/index.php?title=Special%3ATranslate&group=Centralnotice-tgroup-read_only_banner&task=view&language=&filter=&action=translate translation or proofreading] of this banner text.</span> '''நீங்கள் ஒரு குறுகிய காலத்திற்கு அனைத்து விக்கிகளையும் படிக்க முடியும், ஆனால் தொகுக்க முடியாது.''' * செவ்வாய், {{#time:l j xg Y|2026-10-28|ta}} நீங்கள் அதிக பட்சமாக ஒரு மணி நேரத்திற்கு தொகுக்க முடியாது. * இந்த நேரங்களில் நீங்கள் தொகுக்கவோ அல்லது சேமிக்கவோ முயன்றால், நீங்கள் ஒரு பிழை செய்தியை காண்பீர். இந்த நிமிடங்களில் எந்த தொகுப்பும் தொலையாது என நம்புகிறோம், ஆனால் எங்களால் அதை உறுதி செய்ய இயலாது. நீங்கள் அந்த பிழை செய்தியை கண்டால், தயவு செய்து எல்லாம் இயல்பு நிலைக்கு திரும்பும் வரை காத்திருக்கவும். அதன் பின் நீங்கள் உங்கள் தொகுப்பை சேமிக்க முடியும். இருப்பினும், முன்னெச்சரிக்கையாக உங்கள் மாற்றங்களை ஓர் பிரதி எடுத்து கொள்வதை நாங்கள் பரிந்துரைக்கிரோம். ''பிற விளைவுகள்'': * பின்புல வேலைகள் மந்தமாக இருக்கும். மேலும் அவற்றில் சில கைவிடப்படலாம். சிகப்பு இணைப்புகள் வழக்கமான வேகத்தில் புதுப்பிக்கப்படாமல் போகலாம். நீங்கள் வேறு எங்கேனும் இணைக்கப்பட்ட ஒரு கட்டுரையை உருவாக்கினால், அந்த இணைப்பு வழக்கத்தை விட அதிக நேரம் சிகப்பாகவே இருக்கும். சில நீண்ட நேரம் ஓடும் துணுக்குகள் நிறுத்தப்பட வேண்டி இருக்கும். * மற்ற வாரங்களைப் போலவே மூல பயன்கொள் நடக்கும் என்று நாங்கள் எதிர்பார்க்கிறோம். இருப்பினும், சில சூழ்நிலைகளில் செயல்பாட்டிற்கு பிறகு தேவைப்பட்டால் மூல முடக்கம் சரியான நேரத்தில் நடக்கலாம். * 90 நிமிடங்களுக்கு [[mw:Special:MyLanguage/GitLab|கிட்லாப் (GitLab)]]-ஐ பயன்படுத்த இயலாது. தேவைப்பட்டால் இந்த திட்டம் ஒத்திவைக்கப்படலாம். நீங்கள் [[wikitech:Switch_Datacenter|அட்டவணையை wikitech.wikimedia.org இல் படிக்கலாம்]]. எந்த மாற்றங்களும் அட்டவணையில் அறிவிக்கப்படும். '''தயவு செய்து இந்த தகவலை உங்கள் சமூகத்துடன் பகிர்ந்து கொள்ளுங்கள்.'''</div><section end="server-switch"/> [[user:Trizek_(WMF)|Trizek_(WMF)]] 14:01, 26 அக்டோபர் 2026 (UTC) <!-- Message sent by User:Trizek (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Distribution_list/Non-Technical_Village_Pumps_distribution_list&oldid=30513874 --> == <span lang="en" dir="ltr">Tech News: 2026-44</span> == <div lang="en" dir="ltr"> <section begin="technews-2026-W44"/><div class="plainlinks"> Latest '''[[m:Special:MyLanguage/Tech/News|tech news]]''' from the Wikimedia technical community. Please tell other users about these changes. Not all changes will affect you. [[m:Special:MyLanguage/Tech/News/2026/44|Translations]] are available. '''Weekly highlight''' * An experiment launched on September 29 is testing [[mw:Special:MyLanguage/Readers/Reader_Experience/Reading_recommendations|article recommendations]] for new account holders who also identify as readers. By suggesting articles to read, similar to recommendations in the Wikipedia app for logged-in readers, the experiment aims to encourage new account holders to read and return to the website regularly, with the goal of increasing internal referrals by 1%. See the related [[m:Community Wishlist/W505|Community Wishlist]] proposal. * Several Wikimedia services were taken offline last week because of a [https://www.php.net/archive/2026.php#2026-10-20-1 security vulnerability in PHP]. This is because they need to be updated before they are safe to put back online. This is not a problem just for the Wikimedia movement but affects a lot of websites. You can follow updates on [[wikitech:Incidents/2026-10-21 PHP security update/Community|Wikitech]]. Cloud VPS and Toolforge will take longer to bring back online. This is to make sure that applications which run on Cloud VPS or Toolforge are not affected. Tool developers can see updates and more information on the [https://lists.wikimedia.org/postorius/lists/cloud-announce.lists.wikimedia.org/ Cloud-announce mailing list]. [https://diff.wikimedia.org/2026/10/21/impact-of-php-vulnerabilities-on-wikimedia-tools-and-services/][https://diff.wikimedia.org/2026/10/21/impact-of-php-vulnerabilities-on-wikimedia-cloud-services/] * [[m:Special:MyLanguage/Tech/News/2026/43|Last week we shared]] that to measure the impact of incorporating some community-requested features on logged-out reader retention, a test for the [[mw:Special:MyLanguage/Readers/Reader Growth/Image Browsing|Image Carousel]] feature would be pushed to logged-out readers. Due to an issue, we will be restarting the test, but it will be live on October 26, not November 20 as initially announced. '''Updates for editors''' * A new [[wikitech:KAPOW|bot detection system]] to better identify automated traffic that disguises itself as human visitors, is coming to the Wikimedia wikis. This might cause sudden changes to visitor statistics starting from 26 October, going back to 20 June 2026. * The [[mw:Special:MyLanguage/Extension:Score|Score extension]] which renders musical notation inside wiki pages, has been temporarily disabled on all wikis due to a security issue. Affected pages will display a notice instead of musical scores, but the underlying code remains intact. No action is needed from editors, and a fix is in progress. A separate announcement will follow when the extension is re-enabled. Read more on [[phab:T441579|Phabricator]]. * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] View all {{formatnum:18}} community-submitted {{PLURAL:18|task|tasks}} that were [[m:Special:MyLanguage/Tech/News/Recently resolved community tasks|resolved last week]]. For example, the issue where CodeMirror syntax highlighter appears to have been enabled in error for existing editors has been fixed. [https://phabricator.wikimedia.org/T441308] '''Updates for technical contributors''' * [[m:Special:MyLanguage/Tech/Server switch|All wikis will be read-only]] for a few minutes on Wednesday, October 28 2026 at [https://zonestamp.toolforge.org/1793199600 15:00 UTC]. This is for the datacenter server switchover backup tests, [[wikitech:Deployments/Yearly calendar|which happen twice a year]]. During the switchover, all Wikimedia website traffic is shifted from one primary data center to the backup data center to test availability and prevent service disruption even in emergencies. [https://phabricator.wikimedia.org/T439703] * [[File:Reload icon with two arrows.svg|12px|link=|class=skin-invert|Recurrent item]] Detailed code updates later this week: [[mw:MediaWiki 1.48/wmf.2|MediaWiki]] '''Meetings and events''' * Scholarship and sponsorship applications for the [https://www.mediawiki.org/wiki/Wikimedia%20Hackathon%202027 2027 Wikimedia Hackathon] will close on Friday, October 30th. Submit your application [https://pretix.eu/wikimedia/Hackathon-2027/ through Pretix] before the deadline! '''''[[m:Special:MyLanguage/Tech/News|Tech news]]''' prepared by [[m:Special:MyLanguage/Tech/News/Writers|Tech News writers]] and posted by [[m:Special:MyLanguage/User:MediaWiki message delivery|bot]]&nbsp;• [[m:Special:MyLanguage/Tech/News#contribute|Contribute]]&nbsp;• [[m:Special:MyLanguage/Tech/News/2026/44|Translate]]&nbsp;• [[m:Tech|Get help]]&nbsp;• [[m:Talk:Tech/News|Give feedback]]&nbsp;• [[m:Global message delivery/Targets/Tech ambassadors|Subscribe or unsubscribe]].'' </div><section end="technews-2026-W44"/> </div> <bdi lang="en" dir="ltr">[[User:MediaWiki message delivery|MediaWiki message delivery]]</bdi> 18:56, 26 அக்டோபர் 2026 (UTC) <!-- Message sent by User:STei (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Global_message_delivery/Targets/Tech_ambassadors&oldid=31199592 --> bm0sb5cn2mmyz01bj4hdtdmobn84j8m அப்புதல் 0 291746 2004690 1241586 2026-10-26T23:03:10Z Neyakkoo 16078 /* {{மொழி|ta}} */ - 2004690 wikitext text/x-wiki ==பொருள்== {{பெயர்ச்சொல்-பகுப்பு|ta}} #பூசுதல் #: அரைபடு மகிலுஞ் சாந்து மப்பி ([[திருவிளையாடற் புராணம்]] நாட்டு. ) # ஒற்றுதல் # [[தாக்கு]]தல் #: இருவரும் புயங்களினப்பி மொத்தினர் ([[வில்லிபுத்தூராழ்வார் பாரதம்]] பதினேழா. ) # கவ்வுதல் #: நாய் அப்பிக்கொண்டு போய்விட்டது # சாத்துதல் #: மலர்த்தார்..அப்ப (பதினொ. கோயிற்றிருப்.) # வாயில் திணித்தல் #: அவல் தேனு மப்பி யமுதுசெயும்({{திருப்பு.}}) ==மொழிபெயர்ப்புகள்== {{சிறு-மொழி|en}} #thigh {{ஆதாரங்கள்-மொழி|ta}} [[பகுப்பு:உடற்செயலியல்]] qacywz22rsl6d9jtl6mdo0is0ickfw4 2004697 2004690 2026-10-26T23:21:11Z Neyakkoo 16078 ஒலிப்புக் கோப்பு இணைப்பு: Ta-அப்புதல்.ogg 2004697 wikitext text/x-wiki =={{மொழி|ta}}== {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}} ==பொருள்== {{பெயர்ச்சொல்-பகுப்பு|ta}} #பூசுதல் #: அரைபடு மகிலுஞ் சாந்து மப்பி ([[திருவிளையாடற் புராணம்]] நாட்டு. ) # ஒற்றுதல் # [[தாக்கு]]தல் #: இருவரும் புயங்களினப்பி மொத்தினர் ([[வில்லிபுத்தூராழ்வார் பாரதம்]] பதினேழா. ) # கவ்வுதல் #: நாய் அப்பிக்கொண்டு போய்விட்டது # சாத்துதல் #: மலர்த்தார்..அப்ப (பதினொ. கோயிற்றிருப்.) # வாயில் திணித்தல் #: அவல் தேனு மப்பி யமுதுசெயும்({{திருப்பு.}}) ==மொழிபெயர்ப்புகள்== {{சிறு-மொழி|en}} #thigh {{ஆதாரங்கள்-மொழி|ta}} [[பகுப்பு:உடற்செயலியல்]] t0nptkoq81xbevc2e9eaztaysswrjdt அப்பல் 0 366369 2004676 1439828 2026-10-26T22:22:03Z Neyakkoo 16078 /* {{மொழி|ta}} */ பயிற்சி 2004676 wikitext text/x-wiki ==பொருள்== {{பெயர்ச்சொல்-பகுப்பு|ta}} #[[அப்புதல்]] ==மொழிபெயர்ப்புகள்== {{சிறு-மொழி|en}} #.. {{ஆதாரங்கள்-மொழி|ta}} ns3y0j2ozvjqqh283hz3tma5aonkdp0 2004684 2004676 2026-10-26T22:50:19Z Neyakkoo 16078 விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}} 2004684 wikitext text/x-wiki =={{மொழி|ta}}== {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}} ==பொருள்== {{பெயர்ச்சொல்-பகுப்பு|ta}} #[[அப்புதல்]] ==மொழிபெயர்ப்புகள்== {{சிறு-மொழி|en}} #.. {{ஆதாரங்கள்-மொழி|ta}} 2g2bapekxc2a0lbmrx3r8xwjpncfeu6 2004686 2004684 2026-10-26T22:58:57Z Neyakkoo 16078 /* {{மொழி|ta}} */ - பயிற்சி 2004686 wikitext text/x-wiki ==பொருள்== {{பெயர்ச்சொல்-பகுப்பு|ta}} #[[அப்புதல்]] ==மொழிபெயர்ப்புகள்== {{சிறு-மொழி|en}} #.. {{ஆதாரங்கள்-மொழி|ta}} ns3y0j2ozvjqqh283hz3tma5aonkdp0 2004687 2004686 2026-10-26T22:59:11Z Neyakkoo 16078 விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}} 2004687 wikitext text/x-wiki =={{மொழி|ta}}== {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}} ==பொருள்== {{பெயர்ச்சொல்-பகுப்பு|ta}} #[[அப்புதல்]] ==மொழிபெயர்ப்புகள்== {{சிறு-மொழி|en}} #.. {{ஆதாரங்கள்-மொழி|ta}} 2g2bapekxc2a0lbmrx3r8xwjpncfeu6 2004689 2004687 2026-10-26T23:02:41Z Neyakkoo 16078 /* {{மொழி|ta}} */ - 2004689 wikitext text/x-wiki =={{மொழி|ta}}== {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}} ==பொருள்== {{பெயர்ச்சொல்-பகுப்பு|ta}} #[[அப்புதல்]] ==மொழிபெயர்ப்புகள்== {{சிறு-மொழி|en}} #.. {{ஆதாரங்கள்-மொழி|ta}} 58kpx74gl3lo231artab4akazqezo9k பயனர்:Neyakkoo 2 374165 2004659 1982808 2026-10-26T21:33:12Z Neyakkoo 16078 /* பணியிட முகவரி */ சேர்ப்பு 2004659 wikitext text/x-wiki [[படிமம்:சத்தியராஜ் தங்கச்சாமி.jpg|thumb]] எனது பெயர் முனைவர் த.சத்தியராஜ் (நேயக்கோ). நான் கோயமுத்தூர், ஸ்ரீ கிருஷ்ணா ஆதித்யா கலை மற்றும் அறிவியல் கல்லூரியில் தமிழ் உதவிப்பேராசிரியராகப் பணிபுரிகிறேன். எனக்கு ஆய்வுக்கட்டுரை எழுதுதல், கணினித் தொழில்நுட்பங்களை அறிந்துகொள்ளுதல், தொல்லியல் தரவுகளைத் தேடுதல் ஆகியவற்றில் ஆர்வம் அதிகம். மாணவர்களுக்கு எளிமையாகவும், தொழில்நுட்பத்தைப் பயன்படுத்தி கற்பிக்கவும் என்னால் ஆன சிறு முயற்சிகளை எடுத்து வருகிறேன். விக்கிப்பீடியாவில் சரியான கட்டுரைகளை எழுத முயற்சிக்கிறேன். எனது சொந்த ஊர் இராமநாதபுரம் மாவட்டத்தில் உள்ள பள்ளமோர்க்குளம் ஆகும். முனைவர் '''த.சத்தியராஜ் (எ) நேயக்கோ''' க.மு. (தமிழ்), க.மு. (மொழியியல்), இ.பர்., த.ப.ப., பர். (தமிழ்)<br/> Dr. '''T. Sathiyaraj (Neyakkoo)''' M.A. (Tamil) M.A.(Lingui.) M.Phil. TPT. Ph.D., NET (Tamil). CCMT. Dip. In Sans. DOM<br/> ==:<u>'''My Family contribution details;-'''</u> <br/>== * த. சத்தியராஜ் [https://en.wikipedia.org/wiki/Special:CentralAuth?target=Neyakkoo '''SUL-1''' (Contribution counts in all Wikimedia projects)] * ச. தமிழினி [https://en.wikipedia.org/wiki/Special:CentralAuth?target=Thamizhini+Sathiyaraj '''SUL-1''' (Contribution counts in all Wikimedia projects)] * ச. பாவாணர் [https://en.wikipedia.org/wiki/Special:CentralAuth?target=Pavanar+Sathiyaraj '''SUL-1''' (Contribution counts in all Wikimedia projects)] * இரா. நித்யா [https://en.wikipedia.org/wiki/Special:CentralAuth?target=NithyaSathiyaraj '''SUL-1''' (Contribution counts in all Wikimedia projects)] ==:<u>'''விக்கித் தானியக்கம்'''</u> <br/>== * [https://ta.wikisource.org/s/8yy1 விக்கித் தானியக்கம்] ** பிறகு, [https://hub-paws.wmcloud.org/user/Neyakkoo/lab/tree/Kalladam%20New/01separateWordKalladam1.py?redirects=1 PAWS] ==பணியிட முகவரி== தமிழ் உதவிப்பேராசிரியர்<br/> ஸ்ரீகிருஷ்ணா ஆதித்யா கலை மற்றும் அறிவியல் கல்லூரி<br/> கோயமுத்தூர் - 641 042<br/> Assistant Professor<br/> Department of Tamil<br/> Sri Krishna Adithya College of Arts and Science<br/> Kovaipudur, Coimbatore - 641 042. Tamil Nadu, India<br/> ==தற்போதைய முகவரி== தொகுப்பு 7, கதவு எண் 002 வள்ளலார் குடியிருப்பு<br/> வளாகம் வள்ளலார் நகர்<br/> ஒண்டிப்புதூர், கோயமுத்தூர் - 641 016<br/> தமிழ்நாடு, இந்தியா<br/> Block 7, Door No.2 Vallalar Apartment<br/> Vallalar Street<br/> Ondipudur, Coimbatore - 641 016<br/> ==நிரந்தர முகவரி== த/பெ க.தங்கச்சாமி<br/> பள்ளமோர்க்குளம்<br/> திருப்புல்லாணி (அஞ்சல்)<br/> இராமநாதபுரம் மாவட்டம்<br/> தமிழ்நாடு, இந்தியா - 623532<br/> S/O K.Thangasamy<br/> PallaMorkkulam<br/> Thiruppullani (po)<br/> Ramanathapuram District<br/> Tamilnadu, India - 623532<br/> ==மின்னஞ்சல்== * neyakkoo27@gmail.com * inameditor@gmail.com * sathiyarajt@skacas.ac.in ==இணையப்பக்கங்கள்== * Web of Science ResearcherID: P-9361-2016 * SSRN: https://hq.ssrn.com/UserHome.cfm (Author ID: 3471877) * ResearchGate: https://www.researchgate.net/profile/Sathiyaraj_Thangasamy * Googlescholar: https://scholar.google.co.in/citations?user=TR8U4lUAAAAJ&hl=en * ORCID : https://orcid.org/0000-0002-2272-3117 * Research E_Journal: இனம் (http://www.inamtamil.com/) * Blog: மெய்வேந்து (www.meyveendu.blogspot.in) * Contact: 9600370671,8870163559 ==குறிப்பிடத்தக்க ஈடுபாடுகள்== ==தமிழ்க் கணிமை== * ஆய்வுக்கட்டுரைகள் பதிப்பித்தல் * ஆய்வுநூல்கள், படைப்பு நூல்கள் பதிப்பித்தல் * மின்னூல்கள் உருவாக்கல், வெளியிடல் * வலைப்பூ எழுதுதல் * வலைக்காட்சி * கூகுள் வகுப்பறை * ககூட் விளையாட்டு ==எழுத்து== * ஆய்வுக்கட்டுரைகள் (84) * ஆய்வுநூல்கள் (3) * கவிதை நூல்கள் (4) * பதிப்பு நூல்கள் (5) * கணித்தமிழ் (1) * வரலாறு (1) ==பயிற்றுநர்== * மரபுக்கவிதை * கணித்தமிழ் * மின்னூல் உருவாக்கல் * இணையத்தில் ஆய்வுக்கட்டுரை எழுதுதல் * வலைப்பூ * வலைக்காட்சி * விக்கிமீடியா * தமிழ்மொழி பயிற்றல் 8j7rzl27ocf27u052tzkdd02vcms284 அப்பள் 0 388458 2004699 1928180 2026-10-26T23:31:57Z Neyakkoo 16078 ஒலிப்புக் கோப்பு இணைப்பு: Ta-அப்பள்.ogg 2004699 wikitext text/x-wiki =={{மொழி|ta}}== {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}} {{ஒலிக்கோப்பு-தமிழ்|}} == பொருள் == {{பெயர்ச்சொல்-பகுப்பு|ta}} # கடம்பமரம் == மொழிபெயர்ப்புகள் == <!---மொழிபெயர்ப்புகளைச்சேர்த்த பிறகு, {{மொழிபெயர்ப்புகளைச்சேர்}} என்ற வார்ப்புருவை நீக்கிவிடவும்---> {{மொழிபெயர்ப்புகளைச்சேர்}} {{சிறு-மொழி|en}} {{ஆதாரங்கள்}}{{சித்தமருத்துவஅகராதி}} [[பகுப்பு:நிகண்டியம் திட்டச் சொற்கள்]] [[பகுப்பு:நான்கெழுத்துச் சொற்கள்]] nvwn99npejt994p0m4961c25m3dwucj 2004700 2004699 2026-10-26T23:33:10Z Neyakkoo 16078 /* {{மொழி|ta}} */ + 2004700 wikitext text/x-wiki =={{மொழி|ta}}== {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}} == பொருள் == {{பெயர்ச்சொல்-பகுப்பு|ta}} # கடம்பமரம் == மொழிபெயர்ப்புகள் == <!---மொழிபெயர்ப்புகளைச்சேர்த்த பிறகு, {{மொழிபெயர்ப்புகளைச்சேர்}} என்ற வார்ப்புருவை நீக்கிவிடவும்---> {{மொழிபெயர்ப்புகளைச்சேர்}} {{சிறு-மொழி|en}} {{ஆதாரங்கள்}}{{சித்தமருத்துவஅகராதி}} [[பகுப்பு:நிகண்டியம் திட்டச் சொற்கள்]] [[பகுப்பு:நான்கெழுத்துச் சொற்கள்]] qzir1w6kgpage3zzwlxdqwhm3f51suq ௧ 0 430382 2004702 1986454 2026-10-26T23:42:12Z Neyakkoo 16078 ஒலிப்புக் கோப்பு இணைப்பு: Ta-௧.ogg 2004702 wikitext text/x-wiki [[படிமம்:Tamil One.gif|thumb|100px]] [[படிமம்:Oldtamilcharactersold.jpg|thumb|ஒன்று]] =={{மொழி|ta}}== {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}} {{ஒலிக்கோப்பு-தமிழ்|}} ==பொருள்== {{பெயர்ச்சொல்-பகுப்பு|ta}} #தமிழ் எண் [[ஒன்று]] ஆகும். ==மொழிபெயர்ப்புகள்== {{சிறு-மொழி|en}} :*tamil number one. {{ஆதாரங்கள்-மொழி|ta}} nrysag9p51qp7sx1f5726mg8qni4kdo 2004704 2004702 2026-10-26T23:47:00Z Neyakkoo 16078 /* {{மொழி|ta}} */ - 2004704 wikitext text/x-wiki [[படிமம்:Tamil One.gif|thumb|100px]] [[படிமம்:Oldtamilcharactersold.jpg|thumb|ஒன்று]] =={{மொழி|ta}}== {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}} ==பொருள்== {{பெயர்ச்சொல்-பகுப்பு|ta}} #தமிழ் எண் [[ஒன்று]] ஆகும். ==மொழிபெயர்ப்புகள்== {{சிறு-மொழி|en}} :*tamil number one. {{ஆதாரங்கள்-மொழி|ta}} 5k0u8pw23lp9vl7usp665ar619tcpch பயனர்:Neyakkoo/common.js 2 433154 2004656 2002437 2026-10-26T15:51:40Z Neyakkoo 16078 Neyakkoo/Wiktionary Audio File Linker Tool.js 2004656 javascript text/javascript mw.loader.load( '/w/index.php?title=User:Neyakkoo/engpicturebutton.js&action=raw&ctype=text/javascript' ); // தமிழ் விக்சனரி - ஒலிப்புக் கோப்பு இணைக்கும் கருவி importScript('பயனர்:Neyakkoo/Wiktionary Audio File Linker Tool.js'); 9uci44vagsnoldg75d8ns6bfxy5jlxv 2004691 2004656 2026-10-26T23:05:56Z Neyakkoo 16078 பயிற்சி 2004691 javascript text/javascript mw.loader.load( '/w/index.php?title=User:Neyakkoo/engpicturebutton.js&action=raw&ctype=text/javascript' ); <script src="https://unpkg.com/@ffmpeg/ffmpeg@0.12.10/dist/umd/ffmpeg.js"></script> <script src="https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd/ffmpeg-core.js"></script> // தமிழ் விக்சனரி - ஒலிப்புக் கோப்பு இணைக்கும் கருவி importScript('பயனர்:Neyakkoo/Wiktionary Audio File Linker Tool.js'); 06bh0yxhkhvtt8snss5g24jhceci1fv 2004693 2004691 2026-10-26T23:08:04Z Neyakkoo 16078 பயிற்சி 2004693 javascript text/javascript mw.loader.load( '/w/index.php?title=User:Neyakkoo/engpicturebutton.js&action=raw&ctype=text/javascript' ); // தமிழ் விக்சனரி - ஒலிப்புக் கோப்பு இணைக்கும் கருவி importScript('பயனர்:Neyakkoo/Wiktionary Audio File Linker Tool.js'); 9uci44vagsnoldg75d8ns6bfxy5jlxv மாலைகள் 0 433352 2004674 2004473 2026-10-26T22:09:14Z Neyakkoo 16078 விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|Ta-மாலைகள்,.ogg}} 2004674 wikitext text/x-wiki == {{மொழி|ta}} == {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}} {{விக்கிப்பீடியா-மொழி|ta}} {{clear}} __TOC__ === பொருள் === {{பெயர்ச்சொல்-பகுப்பு|ta}} # [[மாலைகள்]] பற்றிய பொருள். ==வேற்றுமை உருபுகள்== * ஐ = மாலைகளை * ஆல் = மாலைகளால் * கு = மாலைகளுக்கு * அது = மாலைகளின் * இலிருந்து = மாலைகளிலிருந்து * ஓடு = மாலைகளோடு * இன் = மாலைகளின் == இலக்கிய மேற்கோள் == * {{மேற்கோள் தேவை}} == ஒத்த சொற்கள் == * {{விரிவாக்கம் தேவை}} == பிறமொழிச் சொற்கள் == * {{விரிவாக்கம் தேவை}} == சொல்வளம் == * {{விரிவாக்கம் தேவை}} ==பிற வடிவங்கள்== * பெயர்ச்சொல் வடிவங்கள் மேலே உள்ள இலக்கணப் பிரிவுகளில் காண்க. {{ஆதாரங்கள்-மொழி|ta}} [[பகுப்பு:தமிழ்ச் சொற்கள்]] hdndus9umzfq9by8t3dz7dl75782u7a நட்டாோ் 0 433423 2004670 2004624 2026-10-26T21:53:46Z Neyakkoo 16078 விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|Ta-நட்டாோ்.ogg}} 2004670 wikitext text/x-wiki == {{மொழி|ta}} == {{ஒலிக்கோப்பு-தமிழ்|}} {{விக்கிப்பீடியா-மொழி|ta}} {{clear}} __TOC__ === பொருள் === {{பெயர்ச்சொல்-பகுப்பு|ta}} # உறவினா் ==வேற்றுமை உருபுகள்== * ஐ = நட்டோரை * ஆல் = நட்டோரால் * கு = நட்டோருக்கு * அது = நட்டோரது * இலிருந்து = நட்டோரிலிருந்து * ஓடு = நட்டோரோடு * இன் = நட்டோரின் == இலக்கிய மேற்கோள் == * {{மேற்கோள் தேவை}} == ஒத்த சொற்கள் == * {{விரிவாக்கம் தேவை}} == பிறமொழிச் சொற்கள் == * {{விரிவாக்கம் தேவை}} == சொல்வளம் == * {{விரிவாக்கம் தேவை}} ==பிற வடிவங்கள்== * பெயர்ச்சொல் வடிவங்கள் மேலே உள்ள இலக்கணப் பிரிவுகளில் காண்க. {{ஆதாரங்கள்-மொழி|ta}} [[பகுப்பு:தமிழ்ச் சொற்கள்]] 6czgnwpyuqnehfqedit38hz9hybr7fx விதேயன் 0 433434 2004661 2004640 2026-10-26T21:39:06Z Neyakkoo 16078 விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: [[c:File:Ta-விதேயன்.ogg|Ta-விதேயன்.ogg]] 2004661 wikitext text/x-wiki == {{மொழி|ta}} == {{விக்கிப்பீடியா-மொழி|ta}} {{clear}} __TOC__ === ஒலிப்பு === * {{audio|ta|Ta-விதேயன்.ogg|ஒலிப்பு}} === பொருள் === {{பெயர்ச்சொல்-பகுப்பு|ta}} # சொற்படிகேட்பவன் ==பிற வடிவங்கள்== {{விக்கிப்பீடியா|வேற்றுமையுருபு}} * வேற்றுமையுருபுகளால் மாறக்கூடியன. : * விதேயன் + ஐ = விதேயனை : * விதேயன் + ஆல் = விதேயனால் : * விதேயன் + கு = விதேயனுக்கு : * விதேயன் + அது = விதேயனினது : * விதேயன் + இலிருந்து = விதேயனிலிருந்து : * விதேயன் + ஓடு = விதேயனோடு : * விதேயன் + இன் = விதேயனின் == இலக்கிய மேற்கோள் == * {{மேற்கோள் தேவை}} == ஒத்த சொற்கள் == * {{விரிவாக்கம் தேவை}} == பிறமொழிச் சொற்கள் == * {{விரிவாக்கம் தேவை}} == சொல்வளம் == * {{விரிவாக்கம் தேவை}} {{ஆதாரங்கள்-மொழி|ta}} [[பகுப்பு:தமிழ்ச் சொற்கள்]] omjvyvzkz6hgma7q6jy4jjqj9x1p6xf 2004663 2004661 2026-10-26T21:46:29Z Neyakkoo 16078 /* {{மொழி|ta}} */ - பயிற்சி 2004663 wikitext text/x-wiki == {{மொழி|ta}} == {{விக்கிப்பீடியா-மொழி|ta}} {{clear}} __TOC__ === பொருள் === {{பெயர்ச்சொல்-பகுப்பு|ta}} # சொற்படிகேட்பவன் ==பிற வடிவங்கள்== {{விக்கிப்பீடியா|வேற்றுமையுருபு}} * வேற்றுமையுருபுகளால் மாறக்கூடியன. : * விதேயன் + ஐ = விதேயனை : * விதேயன் + ஆல் = விதேயனால் : * விதேயன் + கு = விதேயனுக்கு : * விதேயன் + அது = விதேயனினது : * விதேயன் + இலிருந்து = விதேயனிலிருந்து : * விதேயன் + ஓடு = விதேயனோடு : * விதேயன் + இன் = விதேயனின் == இலக்கிய மேற்கோள் == * {{மேற்கோள் தேவை}} == ஒத்த சொற்கள் == * {{விரிவாக்கம் தேவை}} == பிறமொழிச் சொற்கள் == * {{விரிவாக்கம் தேவை}} == சொல்வளம் == * {{விரிவாக்கம் தேவை}} {{ஆதாரங்கள்-மொழி|ta}} [[பகுப்பு:தமிழ்ச் சொற்கள்]] alssh4p5rliqjt6t6yxyvjb8syq572i 2004664 2004663 2026-10-26T21:46:39Z Neyakkoo 16078 விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு வார்ப்புரு இணைக்கப்பட்டது: [[c:File:Ta-விதேயன்.ogg|Ta-விதேயன்.ogg]] 2004664 wikitext text/x-wiki == {{மொழி|ta}} == {{விக்கிப்பீடியா-மொழி|ta}} {{clear}} __TOC__ === ஒலிப்பு === * {{audio|ta|Ta-விதேயன்.ogg|ஒலிப்பு}} === பொருள் === {{பெயர்ச்சொல்-பகுப்பு|ta}} # சொற்படிகேட்பவன் ==பிற வடிவங்கள்== {{விக்கிப்பீடியா|வேற்றுமையுருபு}} * வேற்றுமையுருபுகளால் மாறக்கூடியன. : * விதேயன் + ஐ = விதேயனை : * விதேயன் + ஆல் = விதேயனால் : * விதேயன் + கு = விதேயனுக்கு : * விதேயன் + அது = விதேயனினது : * விதேயன் + இலிருந்து = விதேயனிலிருந்து : * விதேயன் + ஓடு = விதேயனோடு : * விதேயன் + இன் = விதேயனின் == இலக்கிய மேற்கோள் == * {{மேற்கோள் தேவை}} == ஒத்த சொற்கள் == * {{விரிவாக்கம் தேவை}} == பிறமொழிச் சொற்கள் == * {{விரிவாக்கம் தேவை}} == சொல்வளம் == * {{விரிவாக்கம் தேவை}} {{ஆதாரங்கள்-மொழி|ta}} [[பகுப்பு:தமிழ்ச் சொற்கள்]] qg4fwnwhu7ykih11hk9rv2kukj8r7x4 2004666 2004664 2026-10-26T21:50:31Z Neyakkoo 16078 /* ஒலிப்பு */ பயிற்சி 2004666 wikitext text/x-wiki == {{மொழி|ta}} == {{விக்கிப்பீடியா-மொழி|ta}} {{clear}} __TOC__ === பொருள் === {{பெயர்ச்சொல்-பகுப்பு|ta}} # சொற்படிகேட்பவன் ==பிற வடிவங்கள்== {{விக்கிப்பீடியா|வேற்றுமையுருபு}} * வேற்றுமையுருபுகளால் மாறக்கூடியன. : * விதேயன் + ஐ = விதேயனை : * விதேயன் + ஆல் = விதேயனால் : * விதேயன் + கு = விதேயனுக்கு : * விதேயன் + அது = விதேயனினது : * விதேயன் + இலிருந்து = விதேயனிலிருந்து : * விதேயன் + ஓடு = விதேயனோடு : * விதேயன் + இன் = விதேயனின் == இலக்கிய மேற்கோள் == * {{மேற்கோள் தேவை}} == ஒத்த சொற்கள் == * {{விரிவாக்கம் தேவை}} == பிறமொழிச் சொற்கள் == * {{விரிவாக்கம் தேவை}} == சொல்வளம் == * {{விரிவாக்கம் தேவை}} {{ஆதாரங்கள்-மொழி|ta}} [[பகுப்பு:தமிழ்ச் சொற்கள்]] dp134j1uiddcwp5ii7jd6kjc1wq2zzs 2004667 2004666 2026-10-26T21:50:38Z Neyakkoo 16078 விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|Ta-விதேயன்.ogg}} 2004667 wikitext text/x-wiki == {{மொழி|ta}} == {{ஒலிக்கோப்பு-தமிழ்|Ta-விதேயன்.ogg}} {{விக்கிப்பீடியா-மொழி|ta}} {{clear}} __TOC__ === பொருள் === {{பெயர்ச்சொல்-பகுப்பு|ta}} # சொற்படிகேட்பவன் ==பிற வடிவங்கள்== {{விக்கிப்பீடியா|வேற்றுமையுருபு}} * வேற்றுமையுருபுகளால் மாறக்கூடியன. : * விதேயன் + ஐ = விதேயனை : * விதேயன் + ஆல் = விதேயனால் : * விதேயன் + கு = விதேயனுக்கு : * விதேயன் + அது = விதேயனினது : * விதேயன் + இலிருந்து = விதேயனிலிருந்து : * விதேயன் + ஓடு = விதேயனோடு : * விதேயன் + இன் = விதேயனின் == இலக்கிய மேற்கோள் == * {{மேற்கோள் தேவை}} == ஒத்த சொற்கள் == * {{விரிவாக்கம் தேவை}} == பிறமொழிச் சொற்கள் == * {{விரிவாக்கம் தேவை}} == சொல்வளம் == * {{விரிவாக்கம் தேவை}} {{ஆதாரங்கள்-மொழி|ta}} [[பகுப்பு:தமிழ்ச் சொற்கள்]] axxhasm3cegyyysox75m3725dk3mjfe 2004668 2004667 2026-10-26T21:52:05Z Neyakkoo 16078 /* {{மொழி|ta}} */ + 2004668 wikitext text/x-wiki == {{மொழி|ta}} == {{ஒலிக்கோப்பு-தமிழ்|}} {{விக்கிப்பீடியா-மொழி|ta}} {{clear}} __TOC__ === பொருள் === {{பெயர்ச்சொல்-பகுப்பு|ta}} # சொற்படிகேட்பவன் ==பிற வடிவங்கள்== {{விக்கிப்பீடியா|வேற்றுமையுருபு}} * வேற்றுமையுருபுகளால் மாறக்கூடியன. : * விதேயன் + ஐ = விதேயனை : * விதேயன் + ஆல் = விதேயனால் : * விதேயன் + கு = விதேயனுக்கு : * விதேயன் + அது = விதேயனினது : * விதேயன் + இலிருந்து = விதேயனிலிருந்து : * விதேயன் + ஓடு = விதேயனோடு : * விதேயன் + இன் = விதேயனின் == இலக்கிய மேற்கோள் == * {{மேற்கோள் தேவை}} == ஒத்த சொற்கள் == * {{விரிவாக்கம் தேவை}} == பிறமொழிச் சொற்கள் == * {{விரிவாக்கம் தேவை}} == சொல்வளம் == * {{விரிவாக்கம் தேவை}} {{ஆதாரங்கள்-மொழி|ta}} [[பகுப்பு:தமிழ்ச் சொற்கள்]] 8lb4ddw8ic8ppg9ovixrgfbcvop98i1 2004672 2004668 2026-10-26T22:07:14Z Neyakkoo 16078 பயிற்சி 2004672 wikitext text/x-wiki {{விக்கிப்பீடியா-மொழி|ta}} {{clear}} __TOC__ === பொருள் === {{பெயர்ச்சொல்-பகுப்பு|ta}} # சொற்படிகேட்பவன் ==பிற வடிவங்கள்== {{விக்கிப்பீடியா|வேற்றுமையுருபு}} * வேற்றுமையுருபுகளால் மாறக்கூடியன. : * விதேயன் + ஐ = விதேயனை : * விதேயன் + ஆல் = விதேயனால் : * விதேயன் + கு = விதேயனுக்கு : * விதேயன் + அது = விதேயனினது : * விதேயன் + இலிருந்து = விதேயனிலிருந்து : * விதேயன் + ஓடு = விதேயனோடு : * விதேயன் + இன் = விதேயனின் == இலக்கிய மேற்கோள் == * {{மேற்கோள் தேவை}} == ஒத்த சொற்கள் == * {{விரிவாக்கம் தேவை}} == பிறமொழிச் சொற்கள் == * {{விரிவாக்கம் தேவை}} == சொல்வளம் == * {{விரிவாக்கம் தேவை}} {{ஆதாரங்கள்-மொழி|ta}} [[பகுப்பு:தமிழ்ச் சொற்கள்]] rgsomds50jg2i6d0teo81ver4hfe80g 2004673 2004672 2026-10-26T22:07:23Z Neyakkoo 16078 விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|Ta-விதேயன்.ogg}} 2004673 wikitext text/x-wiki =={{மொழி|ta}}== {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}} {{விக்கிப்பீடியா-மொழி|ta}} {{clear}} __TOC__ === பொருள் === {{பெயர்ச்சொல்-பகுப்பு|ta}} # சொற்படிகேட்பவன் ==பிற வடிவங்கள்== {{விக்கிப்பீடியா|வேற்றுமையுருபு}} * வேற்றுமையுருபுகளால் மாறக்கூடியன. : * விதேயன் + ஐ = விதேயனை : * விதேயன் + ஆல் = விதேயனால் : * விதேயன் + கு = விதேயனுக்கு : * விதேயன் + அது = விதேயனினது : * விதேயன் + இலிருந்து = விதேயனிலிருந்து : * விதேயன் + ஓடு = விதேயனோடு : * விதேயன் + இன் = விதேயனின் == இலக்கிய மேற்கோள் == * {{மேற்கோள் தேவை}} == ஒத்த சொற்கள் == * {{விரிவாக்கம் தேவை}} == பிறமொழிச் சொற்கள் == * {{விரிவாக்கம் தேவை}} == சொல்வளம் == * {{விரிவாக்கம் தேவை}} {{ஆதாரங்கள்-மொழி|ta}} [[பகுப்பு:தமிழ்ச் சொற்கள்]] howe4527y56pvqgxm5e0j1hzqoaevbd பயனர்:Neyakkoo/Wiktionary Audio File Linker Tool.js 2 433439 2004655 2026-10-26T14:15:13Z Neyakkoo 16078 Wiki Audio Tool 2004655 javascript text/javascript // ============================================================ // Wiktionary Audio File Linker Tool // தமிழ் விக்சனரி சொற்களுக்கு விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்புகளை இணைக்கும் கருவி // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) / MediaWiki Assistant // ============================================================ (function () { 'use strict'; const TARGET_LANG = 'ta'; const SCRIPT_RE = /[\u0B80-\u0BFF]/; // தமிழ் எழுத்துக்கள் // ---------- கருவியின் லோகோ (SVG) ---------- const LOGO_SIZE = 24; const AUDIO_LOGO_SVG = '' + '<svg xmlns="http://www.w3.org/2000/svg" width="' + LOGO_SIZE + '" height="' + LOGO_SIZE + '" ' + 'viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ' + 'style="vertical-align:middle;flex-shrink:0;display:inline-block;">' + '<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" fill="#00a38f" stroke="#00a38f"/>' + '<path d="M19.07 4.93a10 10 0 0 1 0 14.14" stroke="#00a38f"/>' + '<path d="M15.54 8.46a5 5 0 0 1 0 7.07" stroke="#00a38f"/>' + '</svg>'; function buildButtonLabel(text) { return '<span style="display:inline-flex;align-items:center;gap:6px;font-weight:600;">' + AUDIO_LOGO_SVG + ' ' + mw.html.escape(text) + '</span>'; } let extractedWordsList = []; let audioMatches = []; let csrfToken = ''; // ---------------- 1. பக்கத்திலிருந்து சொற்களைப் பிரித்தெடுத்தல் ---------------- function extractWordsFromPage() { const contentArea = $('#mw-content-text').clone(); contentArea.find('.printfooter, .mw-editsection, style, script, .no-audio-search').remove(); const foundWords = new Set(); contentArea.find('a').each(function () { const word = $(this).text().trim(); if (word && SCRIPT_RE.test(word) && !word.includes(':')) { foundWords.add(word.normalize('NFC')); } }); return Array.from(foundWords); } // ---------------- 2. விக்கிப் பொதுவகத்தில் (Commons) ஒலிப்புக் கோப்புகளைத் தேடுதல் ---------------- async function searchCommonsAudioFiles(words) { const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const matchedResults = []; for (const word of words) { try { // LL-Q1378 (Tamil) கோப்புகளை அல்லது பொதுவான தமிழ் ஒலிப்புக் கோப்புகளைத் தேடுதல் const searchQueries = [ `File:LL-Q1378 (tam)-* -${word}.wav`, `File:LL-Q1378 (tam)-* -${word}.ogg`, `File:Ta-${word}.ogg`, `File:Ta-${word}.wav`, `File:${word}.ogg`, `File:${word}.wav` ]; const searchQuery = `filenamespace:6 "${word}" AND (filetype:audio/ogg OR filetype:audio/wav OR filetype:audio/flac)`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 3, format: 'json' }); if (res && res.query && res.query.search && res.query.search.length > 0) { // சரியான சொல் பொருந்துகிறதா எனக் கூர்ந்து சரிபார்த்தல் const matchedFile = res.query.search.find(item => item.title.toLowerCase().includes(word.toLowerCase()) ) || res.query.search[0]; if (matchedFile) { const fileName = matchedFile.title.replace(/^File:/i, ''); matchedResults.push({ word: word, fileName: fileName }); } } } catch (e) { console.warn(`Audio search failed for: ${word}`, e); } } return matchedResults; } // ---------------- 3. விக்சனரிப் பக்கத்தில் ஒலிப்புக் கோப்பை இணைத்தல் ---------------- async function appendAudioToWiktionaryPage(word, fileName) { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: word, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) { return { success: false, reason: 'பக்கம் கிடைக்கவில்லை' }; } let content = pageData.query.pages[0].revisions[0].content; // ஏற்கனவே ஒலிப்புக்கோப்பு உள்ளதா எனச் சரிபார்த்தல் if (content.includes(fileName) || content.includes('{{ஒலிப்பு}}') || content.includes('{{audio|')) { return { success: false, reason: 'ஏற்கனவே ஒலிப்புக்கோப்பு உள்ளது' }; } // ஒலிப்பு வார்ப்புருவை உருவாக்கம் செய்தல் (Template:audio) const audioWikitext = `\n* {{audio|ta|${fileName}|ஒலிப்பு}}\n`; // === ஒலிப்பு === அல்லது === பொருள் === தலைப்புக்குக் கீழ் சேர்த்தல் if (content.includes('=== ஒலிப்பு ===')) { content = content.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===${audioWikitext}`); } else if (content.includes('=== பொருள் ===')) { content = content.replace('=== பொருள் ===', `=== ஒலிப்பு ===${audioWikitext}\n=== பொருள் ===`); } else { content = audioWikitext + content; } // புதுப்பித்தல் (Save edit) await wtApi.postWithToken('csrf', { action: 'edit', title: word, text: content, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: [[File:${fileName}]] (ஒலிப்புக் கருவி வாயிலாக)` }); return { success: true }; } catch (err) { return { success: false, reason: err.message || err }; } } // ---------------- 4. பயனர் இடைமுக முகப்புப் பெட்டி (UI Modal) ---------------- function showAudioModal(matches) { $('#wikt-audio-modal-overlay').remove(); const overlay = $('<div>', { id: 'wikt-audio-modal-overlay' }).css({ 'position': 'fixed', 'top': '0', 'left': '0', 'width': '100%', 'height': '100%', 'background': 'rgba(0,0,0,0.5)', 'z-index': '10000', 'display': 'flex', 'align-items': 'center', 'justify-content': 'center', 'font-family': 'sans-serif' }); const dialog = $('<div>').css({ 'background': '#fff', 'width': '600px', 'max-width': '95%', 'max-height': '85vh', 'overflow-y': 'auto', 'padding': '20px', 'border-radius': '8px', 'box-shadow': '0 4px 15px rgba(0,0,0,0.3)' }); const title = $('<h3>').css({ 'margin-top': '0', 'color': '#00a38f', 'border-bottom': '2px solid #00a38f', 'padding-bottom': '8px' }) .html(buildButtonLabel(`கண்டறியப்பட்ட ஒலிப்புக் கோப்புகள் (${matches.length})`)); const listContainer = $('<div>').css({ 'margin': '15px 0', 'max-height': '350px', 'overflow-y': 'auto' }); matches.forEach((item, index) => { const row = $('<div>').css({ 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between', 'padding': '8px', 'border-bottom': '1px solid #eee', 'font-size': '13px' }).append( $('<span>').html(`<b>${index + 1}. ${item.word}</b> → <a href="https://commons.wikimedia.org/wiki/File:${encodeURIComponent(item.fileName)}" target="_blank">${item.fileName}</a>`), $('<button>', { text: 'இணை' }).css({ 'padding': '4px 10px', 'background': '#0078d7', 'color': '#fff', 'border': 'none', 'border-radius': '4px', 'cursor': 'pointer', 'font-size': '12px' }).on('click', async function () { $(this).prop('disabled', true).text('இணைக்கப்படுகிறது...'); const res = await appendAudioToWiktionaryPage(item.word, item.fileName); if (res.success) { $(this).css('background', 'green').text('இணைக்கப்பட்டது!'); } else { $(this).css('background', 'red').text(res.reason || 'பிழை'); } }) ); listContainer.append(row); }); const closeBtn = $('<button>', { text: 'மூடு' }).css({ 'padding': '8px 16px', 'background': '#72777d', 'color': '#fff', 'border': 'none', 'border-radius': '4px', 'cursor': 'pointer', 'float': 'right' }).on('click', () => overlay.remove()); dialog.append(title, listContainer, closeBtn); overlay.append(dialog); $('body').append(overlay); } // ---------------- 5. கருவியை இயக்குதல் (Init & Trigger) ---------------- async function processAudioLinking() { const words = extractWordsFromPage(); if (!words.length) { alert('இப்பக்கத்தில் சொற்கள் எதுவும் கண்டறியப்படவில்லை.'); return; } const $btn = $('#wikt-audio-link-btn'); $btn.html(buildButtonLabel('தேடப்படுகிறது...')).prop('disabled', true); const matches = await searchCommonsAudioFiles(words); $btn.html(buildButtonLabel('ஒலிப்புக் கோப்பு இணை')).prop('disabled', false); if (!matches.length) { alert('இப்பக்கத்திலுள்ள சொற்களுக்கு விக்கிப் பொதுவகத்தில் ஒலிப்புக் கோப்புகள் எதுவும் கிடைக்கவில்லை.'); return; } showAudioModal(matches); } function addAudioLinkButton() { if ($('#wikt-audio-link-btn').length > 0) return; const btn = $('<button>', { id: 'wikt-audio-link-btn', type: 'button', title: 'இப்பக்கச் சொற்களுக்கு விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்புகளைத் தேடி இணை' }) .html(buildButtonLabel('ஒலிப்புக் கோப்பு இணை')) .css({ 'margin': '8px 5px', 'padding': '6px 12px', 'background-color': '#ffffff', 'color': '#00a38f', 'border': '1px solid #00a38f', 'border-radius': '6px', 'cursor': 'pointer', 'font-size': '13px', 'box-shadow': '0 2px 4px rgba(0,0,0,0.1)' }) .on('click', processAudioLinking); const $target = $('#firstHeading').length ? $('#firstHeading') : $('#bodyContent'); $target.first().append(btn); } function init() { if (!mw || !mw.loader) return; mw.loader.using(['mediawiki.api', 'mediawiki.ForeignApi', 'jquery']) .then(function () { $(document).ready(addAudioLinkButton); }); } init(); })(); 1ic45zzowa74as488ksugc3swribpqo 2004657 2004655 2026-10-26T15:58:39Z Neyakkoo 16078 பயிற்சி 2004657 javascript text/javascript // ============================================================ // Wiktionary Audio File Linker Tool (Fast Edition) // தமிழ் விக்சனரி சொற்களுக்கு விக்கிப் பொதுவகத்திலிருந்து நொடிகளில் ஒலிப்புக் கோப்புகளை இணைக்கும் கருவி // ============================================================ (function () { 'use strict'; const SCRIPT_RE = /[\u0B80-\u0BFF]/; // தமிழ் எழுத்துக்கள் const LOGO_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#00a38f" stroke-width="2"><polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5" fill="#00a38f"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/></svg>'; // 1. பக்கத்தில் உள்ள தமிழ்ச் சொற்களைப் பிரித்தல் function extractWordsFromPage() { const contentArea = $('#mw-content-text').clone(); contentArea.find('.printfooter, .mw-editsection, style, script, .no-audio-search').remove(); const foundWords = new Set(); contentArea.find('a').each(function () { const word = $(this).text().trim(); if (word && SCRIPT_RE.test(word) && !word.includes(':')) { foundWords.add(word.normalize('NFC')); } }); return Array.from(foundWords); } // 2. ஒரே தேடலில் அதிவேகமாக விக்கிப் பொதுவகத்தில் தேடுதல் (Batch Search) async function searchCommonsAudioFilesFast(words) { if (!words.length) return []; const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); // சொற்களை OR குறியீட்டுடன் சேர்த்து ஒரே API அழைப்பாக அனுப்புதல் const queryTerms = words.map(w => `"${w}"`).join(' OR '); const searchQuery = `filetype:audio (${queryTerms})`; try { const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 50, // அதிகபட்ச கோப்புகளை ஒரே நேரத்தில் எடுத்தல் format: 'json' }); const hits = (res && res.query && res.query.search) || []; const matchedResults = []; // பெறப்பட்ட முடிவுகளைச் சொற்களோடு வேகமாகப் பொருத்துதல் words.forEach(word => { const matchedFile = hits.find(item => item.title.toLowerCase().includes(word.toLowerCase()) ); if (matchedFile) { matchedResults.push({ word: word, fileName: matchedFile.title.replace(/^File:/i, '') }); } }); return matchedResults; } catch (e) { console.warn('Fast Audio Search Failed:', e); return []; } } // 3. விக்சனரிப் பக்கத்தில் ஒலிப்புக் கோப்பைச் சேர்த்தல் async function appendAudioToWiktionaryPage(word, fileName) { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: word, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) { return { success: false, reason: 'பக்கம் இல்லை' }; } let content = pageData.query.pages[0].revisions[0].content; if (content.includes(fileName) || content.includes('{{audio|')) { return { success: false, reason: 'ஏற்கனவே உள்ளது' }; } const audioWikitext = `\n* {{audio|ta|${fileName}|ஒலிப்பு}}\n`; if (content.includes('=== ஒலிப்பு ===')) { content = content.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===${audioWikitext}`); } else if (content.includes('=== பொருள் ===')) { content = content.replace('=== பொருள் ===', `=== ஒலிப்பு ===${audioWikitext}\n=== பொருள் ===`); } else { content = audioWikitext + content; } await wtApi.postWithToken('csrf', { action: 'edit', title: word, text: content, summary: `விக்கிப் பொதுவக ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: [[File:${fileName}]]` }); return { success: true }; } catch (err) { return { success: false, reason: err.message || err }; } } // 4. மேல்தோன்றும் உரையாடல் பெட்டி (Modal UI) function showAudioModal(matches) { $('#wikt-audio-modal-overlay').remove(); const overlay = $('<div>', { id: 'wikt-audio-modal-overlay' }).css({ 'position': 'fixed', 'top': '0', 'left': '0', 'width': '100%', 'height': '100%', 'background': 'rgba(0,0,0,0.5)', 'z-index': '10000', 'display': 'flex', 'align-items': 'center', 'justify-content': 'center', 'font-family': 'sans-serif' }); const dialog = $('<div>').css({ 'background': '#fff', 'width': '550px', 'max-width': '95%', 'max-height': '80vh', 'overflow-y': 'auto', 'padding': '20px', 'border-radius': '8px', 'box-shadow': '0 4px 15px rgba(0,0,0,0.3)' }); const title = $('<h3>').css({ 'margin-top': '0', 'color': '#00a38f', 'border-bottom': '2px solid #00a38f', 'padding-bottom': '8px' }) .html(`${LOGO_SVG} கண்டறியப்பட்ட கோப்புகள் (${matches.length})`); const listContainer = $('<div>').css({ 'margin': '15px 0', 'max-height': '300px', 'overflow-y': 'auto' }); matches.forEach((item, index) => { const row = $('<div>').css({ 'display': 'flex', 'align-items': 'center', 'justify-content': 'space-between', 'padding': '8px', 'border-bottom': '1px solid #eee', 'font-size': '13px' }).append( $('<span>').html(`<b>${index + 1}. ${item.word}</b> → <a href="https://commons.wikimedia.org/wiki/File:${encodeURIComponent(item.fileName)}" target="_blank">${item.fileName}</a>`), $('<button>', { text: 'இணை' }).css({ 'padding': '4px 10px', 'background': '#0078d7', 'color': '#fff', 'border': 'none', 'border-radius': '4px', 'cursor': 'pointer', 'font-size': '12px' }).on('click', async function () { $(this).prop('disabled', true).text('சேர்க்கிறது...'); const res = await appendAudioToWikitionaryPage(item.word, item.fileName); if (res.success) { $(this).css('background', 'green').text('இணைக்கப்பட்டது!'); } else { $(this).css('background', 'red').text(res.reason || 'பிழை'); } }) ); listContainer.append(row); }); const closeBtn = $('<button>', { text: 'மூடு' }).css({ 'padding': '6px 14px', 'background': '#72777d', 'color': '#fff', 'border': 'none', 'border-radius': '4px', 'cursor': 'pointer', 'float': 'right' }).on('click', () => overlay.remove()); dialog.append(title, listContainer, closeBtn); overlay.append(dialog); $('body').append(overlay); } // 5. கருவி இயக்கம் async function processAudioLinking() { const words = extractWordsFromPage(); if (!words.length) { alert('இப்பக்கத்தில் சொற்கள் எதுவும் கண்டறியப்படவில்லை.'); return; } const $btn = $('#wikt-audio-link-btn'); $btn.html('தேடப்படுகிறது...').prop('disabled', true); // அதிவேகத் தேடல் const matches = await searchCommonsAudioFilesFast(words); $btn.html(`${LOGO_SVG} ஒலிப்புக் கோப்பு இணை`).prop('disabled', false); if (!matches.length) { alert('இப்பக்கத்துச் சொற்களுக்கு விக்கிப் பொதுவகத்தில் ஒலிப்புக் கோப்புகள் எதுவும் கிடைக்கவில்லை.'); return; } showAudioModal(matches); } function addAudioLinkButton() { if ($('#wikt-audio-link-btn').length > 0) return; const btn = $('<button>', { id: 'wikt-audio-link-btn', type: 'button', title: 'சொற்களுக்கான ஒலிப்புக் கோப்புகளை அதிவேகமாகத் தேடு' }) .html(`${LOGO_SVG} <span style="vertical-align:middle;margin-left:4px;">ஒலிப்புக் கோப்பு இணை</span>`) .css({ 'margin': '8px 5px', 'padding': '5px 10px', 'background-color': '#fff', 'color': '#00a38f', 'border': '1px solid #00a38f', 'border-radius': '6px', 'cursor': 'pointer', 'font-size': '13px', 'display': 'inline-flex', 'align-items': 'center' }) .on('click', processAudioLinking); const $target = $('#firstHeading').length ? $('#firstHeading') : $('#bodyContent'); $target.first().append(btn); } mw.loader.using(['mediawiki.api', 'mediawiki.ForeignApi', 'jquery'], function () { $(document).ready(addAudioLinkButton); }); })(); tm9kuacjmac6d6bu0qzwu8ivjvugow1 2004660 2004657 2026-10-26T21:38:17Z Neyakkoo 16078 பயிற்சி 2004660 javascript text/javascript // ============================================================ // Wiktionary Single Page Audio Auto-Linker // தற்போதைய விக்சனரிச் சொல் பக்கத்தில் ஒலிப்புக் கோப்பு இல்லையெனில் // விக்கிப் பொதுவகத்திலிருந்து கண்டறிந்து உடனடியாக இணைக்கும் கருவி // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; // 1. முதன்மைப் பெயர்வெளியில் (Main Namespace) உள்ள சொல் பக்கங்களில் மட்டுமே இயங்கும் if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); // தற்போதைய பக்கத்தின் சொல் const SCRIPT_RE = /[\u0B80-\u0BFF]/; // தமிழ் எழுத்துக்கள் சரிபார்ப்பு if (!SCRIPT_RE.test(currentWord)) return; // தமிழ் சொல் இல்லை எனில் இயங்காது // 2. பக்கத்தின் உள்ளடக்கத்தை ஆய்வு செய்து ஒலிப்புக் கோப்பு உள்ளதா எனப் பார்த்தல் async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; // ஏற்கனவே ஒலிப்புக் கோப்பு அல்லது வார்ப்புரு இருந்தால் கருவி இயங்காது if (content.includes('{{audio|') || content.includes('{{ஒலிப்பு}}') || content.includes('.ogg') || content.includes('.wav')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு உள்ளது.'); return; } // 3. விக்கிப் பொதுவகத்தில் (Commons) ஒலிப்புக் கோப்பைத் தேடுதல் const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; // சொல் பெயர் சரியாகப் பொருந்தும் கோப்பைக் கண்டறிதல் for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } // 4. பொதுவகத்தில் கோப்பு இருந்தால் பக்கத்தில் இணைக்கும் பொத்தானைக் காட்டுதல் if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } // 5. ஒலிப்புக் கோப்பு கண்டறியப்பட்டால் தலைப்பிற்கு அருகில் பொத்தான் அமைத்தல் function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகத்தில் கோப்பு உள்ளது: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px', 'box-shadow': '0 2px 4px rgba(0,0,0,0.15)' }) .on('click', async function () { const $this = $(this); $this.prop('disabled', true).text('இணைக்கப்படுகிறது...'); const audioWikitext = `\n* {{audio|ta|${fileName}|ஒலிப்பு}}\n`; let newContent = currentContent; if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===${audioWikitext}`); } else if (newContent.includes('=== பொருள் ===')) { newContent = newContent.replace('=== பொருள் ===', `=== ஒலிப்பு ===${audioWikitext}\n=== பொருள் ===`); } else { newContent = audioWikitext + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: currentWord, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: [[c:File:${fileName}|${fileName}]]` }); $this.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); // 1 நொடியில் பக்கத்தைப் புதுப்பித்தல் } catch (err) { $this.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } }); $('#firstHeading').append(btn); } // பக்கம் தயாரானதும் இயங்குதல் $(document).ready(checkAndLinkAudio); })(); q16b5s6qsc67fcaf4uv0dcsut7s6mt4 2004662 2004660 2026-10-26T21:45:49Z Neyakkoo 16078 பயிற்சி 2004662 javascript text/javascript // ============================================================ // Wiktionary Audio Linker - Standard Template Edition // தமிழ் விக்சனரி சொற்களுக்குச் சரியான {{audio}} வார்ப்புருவுடன் // விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்புகளை இணைக்கும் கருவி // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; // 1. முதன்மைப் பெயர்வெளியில் (Main Namespace) உள்ள சொல் பக்கங்களில் மட்டுமே இயங்கும் if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); // தற்போதைய பக்கத்தின் சொல் const SCRIPT_RE = /[\u0B80-\u0BFF]/; // தமிழ் எழுத்துக்கள் சரிபார்ப்பு if (!SCRIPT_RE.test(currentWord)) return; // 2. பக்கத்தின் உள்ளடக்கத்தைச் சரிபார்த்தல் async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; // ஏற்கனவே சரியான {{audio|...}} வார்ப்புரு இருந்தால் இயங்காது if (content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே சரியான ஒலிப்பு வார்ப்புரு உள்ளது.'); return; } // 3. விக்கிப் பொதுவகத்தில் (Commons) கோப்பு உள்ளதா எனத் தேடுதல் const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; // சொல் பெயர் சரியாகப் பொருந்தும் கோப்பைக் கண்டறிதல் for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } // 4. கோப்பு இருந்தால் பொத்தானைக் காட்டுதல் if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } // 5. சரியான விக்கிநடை வார்ப்புருவுடன் இணைக்கும் பொத்தான் function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px', 'box-shadow': '0 2px 4px rgba(0,0,0,0.15)' }) .on('click', async function () { const $this = $(this); $this.prop('disabled', true).text('இணைக்கப்படுகிறது...'); // தமிழ் விக்சனரியின் சரியான ஒலிப்பு வார்ப்புரு அமைப்பு const audioWikitext = `\n=== ஒலிப்பு ===\n* {{audio|ta|${fileName}|ஒலிப்பு}}\n`; let newContent = currentContent; // ஏற்கனவே பிழையான உரை வடிவில் '* Ta-சொல்.ogg (கோப்பு)' இருந்தால் அதை நீக்குதல் newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); // சரியான இடத்தில் வார்ப்புருவை இணைத்தல் if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* {{audio|ta|${fileName}|ஒலிப்பு}}`); } else if (newContent.includes('=== பொருள் ===')) { newContent = newContent.replace('=== பொருள் ===', `${audioWikitext}\n=== பொருள் ===`); } else { newContent = audioWikitext + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: currentWord, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு வார்ப்புரு இணைக்கப்பட்டது: [[c:File:${fileName}|${fileName}]]` }); $this.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); } catch (err) { $this.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } }); $('#firstHeading').append(btn); } $(document).ready(checkAndLinkAudio); })(); sfa54xkw7kro9md24wmimf2nvv3yjwh 2004665 2004662 2026-10-26T21:50:16Z Neyakkoo 16078 பயிற்சி 2004665 javascript text/javascript // ============================================================ // Wiktionary Audio Linker - Official Tamil Wiktionary Format // தமிழ் விக்சனரி சொற்களுக்குக் குறிப்பிட்ட மரபுச் சட்டகத்தில் // {{ஒலிக்கோப்பு-தமிழ்|...}} வார்ப்புருவுடன் ஒலிப்பு இணைக்கும் கருவி // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; // 1. முதன்மைப் பெயர்வெளியில் (Main Namespace) உள்ள சொல் பக்கங்களில் மட்டுமே இயங்கும் if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); // தற்போதைய பக்கத்தின் சொல் const SCRIPT_RE = /[\u0B80-\u0BFF]/; // தமிழ் எழுத்துக்கள் சரிபார்ப்பு if (!SCRIPT_RE.test(currentWord)) return; // 2. பக்கத்தின் உள்ளடக்கத்தைச் சரிபார்த்தல் async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; // ஏற்கனவே ஒலிப்புக் கோப்பு அல்லது வார்ப்புரு இருந்தால் இயங்காது if (content.includes('{{ஒலிக்கோப்பு-தமிழ்') || content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு வார்ப்புரு உள்ளது.'); return; } // 3. விக்கிப் பொதுவகத்தில் (Commons) கோப்பு உள்ளதா எனத் தேடுதல் const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; // சொல் பெயர் சரியாகப் பொருந்தும் கோப்பைக் கண்டறிதல் for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } // 4. கோப்பு இருந்தால் பொத்தானைக் காட்டுதல் if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } // 5. குறிப்பிட்ட மரபுச் சட்டகத்தில் இணைக்கும் பொத்தான் function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px', 'box-shadow': '0 2px 4px rgba(0,0,0,0.15)' }) .on('click', async function () { const $this = $(this); $this.prop('disabled', true).text('இணைக்கப்படுகிறது...'); const targetAudioTemplate = `{{ஒலிக்கோப்பு-தமிழ்|${fileName}}}`; let newContent = currentContent; // பிழையான உரை வடிவங்கள் இருந்தால் நீக்குதல் newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); // =={{மொழி|ta}}== தலைப்பு இருந்தால் அதன் கீழ் உடனே வைத்தல் if (newContent.includes('=={{மொழி|ta}}==')) { newContent = newContent.replace('=={{மொழி|ta}}==', `=={{மொழி|ta}}==\n\n${targetAudioTemplate}`); } // '== {{மொழி|ta}} ==' இடைவெளிகளுடன் இருந்தால் else if (newContent.includes('== {{மொழி|ta}} ==')) { newContent = newContent.replace('== {{மொழி|ta}} ==', `== {{மொழி|ta}} ==\n\n${targetAudioTemplate}`); } // '=== ஒலிப்பு ===' பகுதி இருந்தால் else if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* ${targetAudioTemplate}`); } // எதுவுமில்லையெனில் பக்கத்தின் தொடக்கத்திலேயே அமைத்தல் else { newContent = `=={{மொழி|ta}}==\n\n${targetAudioTemplate}\n\n` + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: currentWord, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|${fileName}}}` }); $this.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); // 1 நொடியில் பக்கத்தைப் புதுப்பித்தல் } catch (err) { $this.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } }); $('#firstHeading').append(btn); } $(document).ready(checkAndLinkAudio); })(); cjy46tqlbxummfqdviewkd30nu8r5dp 2004669 2004665 2026-10-26T21:53:21Z Neyakkoo 16078 - 2004669 javascript text/javascript // ============================================================ // Wiktionary Audio Linker - Official Tamil Wiktionary Format // தமிழ் விக்சனரி சொற்களுக்குக் குறிப்பிட்ட மரபுச் சட்டகத்தில் // {{ஒலிக்கோப்பு-தமிழ்|...}} வார்ப்புருவுடன் ஒலிப்பு இணைக்கும் கருவி // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; // 1. முதன்மைப் பெயர்வெளியில் (Main Namespace) உள்ள சொல் பக்கங்களில் மட்டுமே இயங்கும் if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); // தற்போதைய பக்கத்தின் சொல் const SCRIPT_RE = /[\u0B80-\u0BFF]/; // தமிழ் எழுத்துக்கள் சரிபார்ப்பு if (!SCRIPT_RE.test(currentWord)) return; // 2. பக்கத்தின் உள்ளடக்கத்தைச் சரிபார்த்தல் async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; // ஏற்கனவே ஒலிப்புக் கோப்பு அல்லது வார்ப்புரு இருந்தால் இயங்காது if (content.includes('{{ஒலிக்கோப்பு-தமிழ்') || content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு வார்ப்புரு உள்ளது.'); return; } // 3. விக்கிப் பொதுவகத்தில் (Commons) கோப்பு உள்ளதா எனத் தேடுதல் const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; // சொல் பெயர் சரியாகப் பொருந்தும் கோப்பைக் கண்டறிதல் for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } // 4. கோப்பு இருந்தால் பொத்தானைக் காட்டுதல் if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } // 5. குறிப்பிட்ட மரபுச் சட்டகத்தில் இணைக்கும் பொத்தான் function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px', 'box-shadow': '0 2px 4px rgba(0,0,0,0.15)' }) .on('click', async function () { const $this = $(this); $this.prop('disabled', true).text('இணைக்கப்படுகிறது...'); const targetAudioTemplate = `{{ஒலிக்கோப்பு-தமிழ்|}}`; let newContent = currentContent; // பிழையான உரை வடிவங்கள் இருந்தால் நீக்குதல் newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); // =={{மொழி|ta}}== தலைப்பு இருந்தால் அதன் கீழ் உடனே வைத்தல் if (newContent.includes('=={{மொழி|ta}}==')) { newContent = newContent.replace('=={{மொழி|ta}}==', `=={{மொழி|ta}}==\n\n${targetAudioTemplate}`); } // '== {{மொழி|ta}} ==' இடைவெளிகளுடன் இருந்தால் else if (newContent.includes('== {{மொழி|ta}} ==')) { newContent = newContent.replace('== {{மொழி|ta}} ==', `== {{மொழி|ta}} ==\n\n${targetAudioTemplate}`); } // '=== ஒலிப்பு ===' பகுதி இருந்தால் else if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* ${targetAudioTemplate}`); } // எதுவுமில்லையெனில் பக்கத்தின் தொடக்கத்திலேயே அமைத்தல் else { newContent = `=={{மொழி|ta}}==\n\n${targetAudioTemplate}\n\n` + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: currentWord, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|${fileName}}}` }); $this.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); // 1 நொடியில் பக்கத்தைப் புதுப்பித்தல் } catch (err) { $this.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } }); $('#firstHeading').append(btn); } $(document).ready(checkAndLinkAudio); })(); 8srlyzjtvhmc5de21ugjoj6vgx45xl0 2004671 2004669 2026-10-26T22:06:47Z Neyakkoo 16078 இந்தியா 2004671 javascript text/javascript // ============================================================ // Wiktionary Audio Linker - Official Tamil Wiktionary Format // தமிழ் விக்சனரி சொற்களுக்குக் குறிப்பிட்ட மரபுச் சட்டகத்தில் // {{ஒலிக்கோப்பு-தமிழ்|...}} வார்ப்புருவுடன் ஒலிப்பு இணைக்கும் கருவி // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; // 1. முதன்மைப் பெயர்வெளியில் (Main Namespace) உள்ள சொல் பக்கங்களில் மட்டுமே இயங்கும் if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); // தற்போதைய பக்கத்தின் சொல் const SCRIPT_RE = /[\u0B80-\u0BFF]/; // தமிழ் எழுத்துக்கள் சரிபார்ப்பு if (!SCRIPT_RE.test(currentWord)) return; // 2. பக்கத்தின் உள்ளடக்கத்தைச் சரிபார்த்தல் async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; // ஏற்கனவே ஒலிப்புக் கோப்பு அல்லது வார்ப்புரு இருந்தால் இயங்காது if (content.includes('{{ஒலிக்கோப்பு-தமிழ்') || content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு வார்ப்புரு உள்ளது.'); return; } // 3. விக்கிப் பொதுவகத்தில் (Commons) கோப்பு உள்ளதா எனத் தேடுதல் const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; // சொல் பெயர் சரியாகப் பொருந்தும் கோப்பைக் கண்டறிதல் for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } // 4. கோப்பு இருந்தால் பொத்தானைக் காட்டுதல் if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } // 5. குறிப்பிட்ட மரபுச் சட்டகத்தில் இணைக்கும் பொத்தான் function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px', 'box-shadow': '0 2px 4px rgba(0,0,0,0.15)' }) .on('click', async function () { const $this = $(this); $this.prop('disabled', true).text('இணைக்கப்படுகிறது...'); const targetAudioTemplate = `{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}`; let newContent = currentContent; // பிழையான உரை வடிவங்கள் இருந்தால் நீக்குதல் newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); // =={{மொழி|ta}}== தலைப்பு இருந்தால் அதன் கீழ் உடனே வைத்தல் if (newContent.includes('=={{மொழி|ta}}==')) { newContent = newContent.replace('=={{மொழி|ta}}==', `=={{மொழி|ta}}==\n\n${targetAudioTemplate}`); } // '== {{மொழி|ta}} ==' இடைவெளிகளுடன் இருந்தால் else if (newContent.includes('== {{மொழி|ta}} ==')) { newContent = newContent.replace('== {{மொழி|ta}} ==', `== {{மொழி|ta}} ==\n\n${targetAudioTemplate}`); } // '=== ஒலிப்பு ===' பகுதி இருந்தால் else if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* ${targetAudioTemplate}`); } // எதுவுமில்லையெனில் பக்கத்தின் தொடக்கத்திலேயே அமைத்தல் else { newContent = `=={{மொழி|ta}}==\n\n${targetAudioTemplate}\n\n` + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: currentWord, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|${fileName}}}` }); $this.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); // 1 நொடியில் பக்கத்தைப் புதுப்பித்தல் } catch (err) { $this.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } }); $('#firstHeading').append(btn); } $(document).ready(checkAndLinkAudio); })(); l41wap5tsl4x9vl3al06wwb64od96ba 2004675 2004671 2026-10-26T22:20:26Z Neyakkoo 16078 பயிற்சி 2004675 javascript text/javascript // ============================================================ // Wiktionary Audio Recorder & Linker // சொல் பக்கத்திலேயே ஒலிப்பதிவு செய்து விக்கிப் பொதுவகத்தில் பதிவேற்றி // விக்சனரிப் பக்கத்தில் {{ஒலிக்கோப்பு-தமிழ்|...}} வார்ப்புருவை இணைக்கும் கருவி // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); const SCRIPT_RE = /[\u0B80-\u0BFF]/; if (!SCRIPT_RE.test(currentWord)) return; let mediaRecorder, audioChunks = [], recordedBlob = null; async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; if (content.includes('{{ஒலிக்கோப்பு-தமிழ்') || content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு உள்ளது.'); return; } const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } else { showRecordAudioButton(content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } // 1. பொதுவகத்தில் ஏற்கனவே உள்ள கோப்பை இணைக்கும் பொத்தான் function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', async function () { $(this).prop('disabled', true).text('இணைக்கப்படுகிறது...'); await updateWiktionaryPage(currentWord, fileName, currentContent, wtApi, $(this)); }); $('#firstHeading').append(btn); } // 2. புதிய ஒலிப்பதிவு செய்யும் பொத்தான் function showRecordAudioButton(currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-record-audio-btn', type: 'button', title: 'சொல்லிற்கான புதிய ஒலிப்பை பதிவு செய்' }) .html(`🎙️ <b>ஒலிப்பதிவு செய்</b>`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#d9534f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', function () { openRecorderModal(currentContent, wtApi); }); $('#firstHeading').append(btn); } // 3. ஒலிப்பதிவு செய்யும் பாப்-அப் பெட்டி (Recorder Modal) function openRecorderModal(currentContent, wtApi) { $('#wikt-recorder-modal').remove(); const modalHtml = ` <div id="wikt-recorder-modal" style="position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000; display:flex; align-items:center; justify-content:center;"> <div style="background:#fff; padding:20px; border-radius:8px; width:380px; text-align:center; box-shadow:0 4px 10px rgba(0,0,0,0.3);"> <h3 style="margin-top:0; color:#333;">🎙️ ஒலிப்பதிவு: <b>${currentWord}</b></h3> <p style="font-size:13px; color:#666;">ஒலிவாங்கியைக் கொண்டு சொல்லைத் தெளிவாகப் பேசவும்.</p> <div style="margin:15px 0;"> <button id="btn-start-rec" style="padding:8px 15px; background:#28a745; color:#fff; border:none; border-radius:4px; cursor:pointer;">▶️ பதிவு தொடங்கு</button> <button id="btn-stop-rec" style="padding:8px 15px; background:#dc3545; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">⏹️ நிறுத்து</button> </div> <audio id="rec-preview" controls style="display:none; width:100%; margin-top:10px;"></audio> <div id="rec-status" style="margin-top:10px; font-size:12px; font-weight:bold;"></div> <div style="margin-top:20px; display:flex; justify-content:space-between;"> <button id="btn-cancel-rec" style="padding:6px 12px; background:#6c757d; color:#fff; border:none; border-radius:4px; cursor:pointer;">ரத்து செய்</button> <button id="btn-upload-rec" style="padding:6px 12px; background:#007bff; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">☁️ பொதுவகத்தில் பதிவேற்று</button> </div> </div> </div>`; $('body').append(modalHtml); $('#btn-start-rec').on('click', async function () { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream); audioChunks = []; mediaRecorder.ondataavailable = event => audioChunks.push(event.data); mediaRecorder.onstop = () => { recordedBlob = new Blob(audioChunks, { type: 'audio/ogg' }); const audioUrl = URL.createObjectURL(recordedBlob); $('#rec-preview').attr('src', audioUrl).show(); $('#btn-upload-rec').show(); $('#rec-status').text('ஒலிப்பதிவு முடிந்தது. கேட்டுப் பார்த்துப் பதிவேற்றவும்.').css('color', 'green'); }; mediaRecorder.start(); $(this).hide(); $('#btn-stop-rec').show(); $('#rec-status').text('பதிவாகிறது... பேசுங்கள்!').css('color', 'red'); } catch (err) { alert('ஒலிவாங்கி (Microphone) அனுமதியை வழங்கவும்.'); } }); $('#btn-stop-rec').on('click', function () { mediaRecorder.stop(); $(this).hide(); $('#btn-start-rec').show().text('🔄 மீண்டும் பதிவு செய்'); }); $('#btn-cancel-rec').on('click', function () { $('#wikt-recorder-modal').remove(); }); $('#btn-upload-rec').on('click', async function () { if (!recordedBlob) return; const $btn = $(this); $btn.prop('disabled', true).text('பதிவேற்றப்படுகிறது...'); $('#rec-status').text('விக்கிப் பொதுவகத்தில் பதிவேற்றப்படுகிறது...').css('color', '#007bff'); const fileName = `LL-Q1378 (tam)-Neyakkoo-${currentWord}.ogg`; const uploaded = await uploadToCommons(fileName, recordedBlob); if (uploaded) { $('#rec-status').text('விக்சனரிப் பக்கத்தில் இணைக்கப்படுகிறது...').css('color', 'green'); await updateWiktionaryPage(currentWord, fileName, currentContent, wtApi); $('#wikt-recorder-modal').remove(); } else { $('#rec-status').text('பதிவேற்றத்தில் பிழை ஏற்பட்டது.').css('color', 'red'); $btn.prop('disabled', false).text('☁️ மீண்டும் முயற்சி செய்'); } }); } // 4. விக்கிப் பொதுவகத்தில் கோப்பை பதிவேற்றுதல் (Commons Upload API) async function uploadToCommons(fileName, fileBlob) { const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); try { const token = await commonsApi.getTokenType('csrf'); const formData = new FormData(); formData.append('action', 'upload'); formData.append('filename', fileName); formData.append('token', token); formData.append('format', 'json'); formData.append('file', fileBlob, fileName); formData.append('comment', `Tamil audio pronunciation for [[ta:${currentWord}]]`); formData.append('text', `== {{int:filedesc}} ==\n{{Information\n|description={{ta|1=தமிழ்: ${currentWord} என்பதன் ஒலிப்பு}}\n|date=${new Date().toISOString().split('T')[0]}\n|source={{own}}\n|author=[[User:Neyakkoo|Dr. Sathiyaraj Thangasamy]]\n}}\n\n[[Category:Tamil pronunciation]]`); const res = await $.ajax({ url: 'https://commons.wikimedia.org/w/api.php', type: 'POST', data: formData, processData: false, contentType: false, xhrFields: { withCredentials: true } }); return res && res.upload && res.upload.result === 'Success'; } catch (e) { console.error('Commons Upload Error:', e); return false; } } // 5. விக்சனரிப் பக்கத்தை புதுப்பித்து சேமித்தல் async function updateWiktionaryPage(word, fileName, currentContent, wtApi, $btnUI) { const targetAudioTemplate = `{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}`; let newContent = currentContent; newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); if (newContent.includes('=={{மொழி|ta}}==')) { newContent = newContent.replace('=={{மொழி|ta}}==', `=={{மொழி|ta}}==\n\n${targetAudioTemplate}`); } else if (newContent.includes('== {{மொழி|ta}} ==')) { newContent = newContent.replace('== {{மொழி|ta}} ==', `== {{மொழி|ta}} ==\n\n${targetAudioTemplate}`); } else if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* ${targetAudioTemplate}`); } else { newContent = `=={{மொழி|ta}}==\n\n${targetAudioTemplate}\n\n` + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: word, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}` }); if ($btnUI) $btnUI.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); } catch (err) { if ($btnUI) $btnUI.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } } $(document).ready(checkAndLinkAudio); })(); 9gop6gsnt1e7kktuyxf9270cgxse4a9 2004677 2004675 2026-10-26T22:26:17Z Neyakkoo 16078 பயிற்சி 2004677 javascript text/javascript // ============================================================ // Wiktionary Audio Recorder & Linker (Fixed Commons Upload) // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); const SCRIPT_RE = /[\u0B80-\u0BFF]/; if (!SCRIPT_RE.test(currentWord)) return; let mediaRecorder, audioChunks = [], recordedBlob = null; async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; if (content.includes('{{ஒலிக்கோப்பு-தமிழ்') || content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு உள்ளது.'); return; } const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } else { showRecordAudioButton(content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', async function () { $(this).prop('disabled', true).text('இணைக்கப்படுகிறது...'); await updateWiktionaryPage(currentWord, fileName, currentContent, wtApi, $(this)); }); $('#firstHeading').append(btn); } function showRecordAudioButton(currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-record-audio-btn', type: 'button', title: 'சொல்லிற்கான புதிய ஒலிப்பை பதிவு செய்' }) .html(`🎙️ <b>ஒலிப்பதிவு செய்</b>`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#d9534f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', function () { openRecorderModal(currentContent, wtApi); }); $('#firstHeading').append(btn); } function openRecorderModal(currentContent, wtApi) { $('#wikt-recorder-modal').remove(); const modalHtml = ` <div id="wikt-recorder-modal" style="position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000; display:flex; align-items:center; justify-content:center;"> <div style="background:#fff; padding:20px; border-radius:8px; width:380px; text-align:center; box-shadow:0 4px 10px rgba(0,0,0,0.3);"> <h3 style="margin-top:0; color:#333;">🎙️ ஒலிப்பதிவு: <b>${currentWord}</b></h3> <p style="font-size:13px; color:#666;">ஒலிவாங்கியைக் கொண்டு சொல்லைத் தெளிவாகப் பேசவும்.</p> <div style="margin:15px 0;"> <button id="btn-start-rec" style="padding:8px 15px; background:#28a745; color:#fff; border:none; border-radius:4px; cursor:pointer;">▶️ பதிவு தொடங்கு</button> <button id="btn-stop-rec" style="padding:8px 15px; background:#dc3545; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">⏹️ நிறுத்து</button> </div> <audio id="rec-preview" controls style="display:none; width:100%; margin-top:10px;"></audio> <div id="rec-status" style="margin-top:10px; font-size:12px; font-weight:bold;"></div> <div style="margin-top:20px; display:flex; justify-content:space-between;"> <button id="btn-cancel-rec" style="padding:6px 12px; background:#6c757d; color:#fff; border:none; border-radius:4px; cursor:pointer;">ரத்து செய்</button> <button id="btn-upload-rec" style="padding:6px 12px; background:#007bff; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">☁️ பொதுவகத்தில் பதிவேற்று</button> </div> </div> </div>`; $('body').append(modalHtml); $('#btn-start-rec').on('click', async function () { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream); audioChunks = []; mediaRecorder.ondataavailable = event => audioChunks.push(event.data); mediaRecorder.onstop = () => { recordedBlob = new Blob(audioChunks, { type: 'audio/ogg' }); const audioUrl = URL.createObjectURL(recordedBlob); $('#rec-preview').attr('src', audioUrl).show(); $('#btn-upload-rec').show(); $('#rec-status').text('ஒலிப்பதிவு முடிந்தது. கேட்டுப் பார்த்துப் பதிவேற்றவும்.').css('color', 'green'); }; mediaRecorder.start(); $(this).hide(); $('#btn-stop-rec').show(); $('#rec-status').text('பதிவாகிறது... பேசுங்கள்!').css('color', 'red'); } catch (err) { alert('ஒலிவாங்கி (Microphone) அனுமதியை வழங்கவும்.'); } }); $('#btn-stop-rec').on('click', function () { mediaRecorder.stop(); $(this).hide(); $('#btn-start-rec').show().text('🔄 மீண்டும் பதிவு செய்'); }); $('#btn-cancel-rec').on('click', function () { $('#wikt-recorder-modal').remove(); }); $('#btn-upload-rec').on('click', async function () { if (!recordedBlob) return; const $btn = $(this); $btn.prop('disabled', true).text('பதிவேற்றப்படுகிறது...'); $('#rec-status').text('விக்கிப் பொதுவகத்தில் பதிவேற்றப்படுகிறது...').css('color', '#007bff'); const fileName = `Ta-${currentWord}.ogg`; const uploaded = await uploadToCommonsFixed(fileName, recordedBlob); if (uploaded) { $('#rec-status').text('விக்சனரிப் பக்கத்தில் இணைக்கப்படுகிறது...').css('color', 'green'); await updateWiktionaryPage(currentWord, fileName, currentContent, wtApi); $('#wikt-recorder-modal').remove(); } else { $('#rec-status').text('பதிவேற்றத்தில் பிழை ஏற்பட்டது.').css('color', 'red'); $btn.prop('disabled', false).text('☁️ மீண்டும் முயற்சி செய்'); } }); } // சரிசெய்யப்பட்ட விக்கிப் பொதுவகப் பதிவேற்றச் செயல்பாடு (mw.ForeignApi முறையில்) async function uploadToCommonsFixed(fileName, fileBlob) { try { const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const pageText = `== {{int:filedesc}} ==\n{{Information\n|description={{ta|1=தமிழ்: ${currentWord} என்பதன் ஒலிப்பு}}\n|date=${new Date().toISOString().split('T')[0]}\n|source={{own}}\n|author=[[User:Neyakkoo|Dr. Sathiyaraj Thangasamy]]\n}}\n\n== {{int:license-header}} ==\n{{CC-BY-SA-4.0}}\n\n[[Category:Tamil pronunciation]]`; const res = await commonsApi.postWithEditToken({ action: 'upload', filename: fileName, file: fileBlob, comment: `Tamil audio pronunciation for [[ta:${currentWord}]]`, text: pageText, ignorewarnings: 1, format: 'json' }); return res && res.upload && res.upload.result === 'Success'; } catch (e) { console.error('Commons Upload Error:', e); return false; } } async function updateWiktionaryPage(word, fileName, currentContent, wtApi, $btnUI) { const targetAudioTemplate = `{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}`; let newContent = currentContent; newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); if (newContent.includes('=={{மொழி|ta}}==')) { newContent = newContent.replace('=={{மொழி|ta}}==', `=={{மொழி|ta}}==\n\n${targetAudioTemplate}`); } else if (newContent.includes('== {{மொழி|ta}} ==')) { newContent = newContent.replace('== {{மொழி|ta}} ==', `== {{மொழி|ta}} ==\n\n${targetAudioTemplate}`); } else if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* ${targetAudioTemplate}`); } else { newContent = `=={{மொழி|ta}}==\n\n${targetAudioTemplate}\n\n` + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: word, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}` }); if ($btnUI) $btnUI.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); } catch (err) { if ($btnUI) $btnUI.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } } $(document).ready(checkAndLinkAudio); })(); srxjmr2wa2ablnxe4886yni25oura65 2004678 2004677 2026-10-26T22:30:34Z Neyakkoo 16078 பயிற்சி 2004678 javascript text/javascript // ============================================================ // Wiktionary Audio Recorder & Linker (Fixed Commons Upload) // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); const SCRIPT_RE = /[\u0B80-\u0BFF]/; if (!SCRIPT_RE.test(currentWord)) return; let mediaRecorder, audioChunks = [], recordedBlob = null; async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; if (content.includes('{{ஒலிக்கோப்பு-தமிழ்') || content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு உள்ளது.'); return; } const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } else { showRecordAudioButton(content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', async function () { $(this).prop('disabled', true).text('இணைக்கப்படுகிறது...'); await updateWiktionaryPage(currentWord, fileName, currentContent, wtApi, $(this)); }); $('#firstHeading').append(btn); } function showRecordAudioButton(currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-record-audio-btn', type: 'button', title: 'சொல்லிற்கான புதிய ஒலிப்பை பதிவு செய்' }) .html(`🎙️ <b>ஒலிப்பதிவு செய்</b>`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#d9534f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', function () { openRecorderModal(currentContent, wtApi); }); $('#firstHeading').append(btn); } function openRecorderModal(currentContent, wtApi) { $('#wikt-recorder-modal').remove(); const modalHtml = ` <div id="wikt-recorder-modal" style="position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000; display:flex; align-items:center; justify-content:center;"> <div style="background:#fff; padding:20px; border-radius:8px; width:380px; text-align:center; box-shadow:0 4px 10px rgba(0,0,0,0.3);"> <h3 style="margin-top:0; color:#333;">🎙️ ஒலிப்பதிவு: <b>${currentWord}</b></h3> <p style="font-size:13px; color:#666;">ஒலிவாங்கியைக் கொண்டு சொல்லைத் தெளிவாகப் பேசவும்.</p> <div style="margin:15px 0;"> <button id="btn-start-rec" style="padding:8px 15px; background:#28a745; color:#fff; border:none; border-radius:4px; cursor:pointer;">▶️ பதிவு தொடங்கு</button> <button id="btn-stop-rec" style="padding:8px 15px; background:#dc3545; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">⏹️ நிறுத்து</button> </div> <audio id="rec-preview" controls style="display:none; width:100%; margin-top:10px;"></audio> <div id="rec-status" style="margin-top:10px; font-size:12px; font-weight:bold;"></div> <div style="margin-top:20px; display:flex; justify-content:space-between;"> <button id="btn-cancel-rec" style="padding:6px 12px; background:#6c757d; color:#fff; border:none; border-radius:4px; cursor:pointer;">ரத்து செய்</button> <button id="btn-upload-rec" style="padding:6px 12px; background:#007bff; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">☁️ பொதுவகத்தில் பதிவேற்று</button> </div> </div> </div>`; $('body').append(modalHtml); $('#btn-start-rec').on('click', async function () { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream); audioChunks = []; mediaRecorder.ondataavailable = event => audioChunks.push(event.data); mediaRecorder.onstop = () => { recordedBlob = new Blob(audioChunks, { type: 'audio/ogg' }); const audioUrl = URL.createObjectURL(recordedBlob); $('#rec-preview').attr('src', audioUrl).show(); $('#btn-upload-rec').show(); $('#rec-status').text('ஒலிப்பதிவு முடிந்தது. கேட்டுப் பார்த்துப் பதிவேற்றவும்.').css('color', 'green'); }; mediaRecorder.start(); $(this).hide(); $('#btn-stop-rec').show(); $('#rec-status').text('பதிவாகிறது... பேசுங்கள்!').css('color', 'red'); } catch (err) { alert('ஒலிவாங்கி (Microphone) அனுமதியை வழங்கவும்.'); } }); $('#btn-stop-rec').on('click', function () { mediaRecorder.stop(); $(this).hide(); $('#btn-start-rec').show().text('🔄 மீண்டும் பதிவு செய்'); }); $('#btn-cancel-rec').on('click', function () { $('#wikt-recorder-modal').remove(); }); $('#btn-upload-rec').on('click', async function () { if (!recordedBlob) return; const $btn = $(this); $btn.prop('disabled', true).text('பதிவேற்றப்படுகிறது...'); $('#rec-status').text('விக்கிப் பொதுவகத்தில் பதிவேற்றப்படுகிறது...').css('color', '#007bff'); const fileName = `Ta-${currentWord}.ogg`; const result = await uploadToCommonsFixed(fileName, recordedBlob); if (result.success) { $('#rec-status').text('விக்சனரிப் பக்கத்தில் இணைக்கப்படுகிறது...').css('color', 'green'); await updateWiktionaryPage(currentWord, fileName, currentContent, wtApi); $('#wikt-recorder-modal').remove(); } else { // குறிப்பிட்ட பிழைத் தகவலைக் காட்டவும் $('#rec-status').text(`பதிவேற்றத்தில் பிழை: ${result.error}`).css('color', 'red'); $btn.prop('disabled', false).text('☁️ மீண்டும் முயற்சி செய்'); } }); } // சரிசெய்யப்பட்ட விக்கிப் பொதுவகப் பதிவேற்றச் செயல்பாடு async function uploadToCommonsFixed(fileName, fileBlob) { try { const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); // 1. பொதுவகத்தில் உள்நுழைந்துள்ளீர்களா எனச் சரிபார்க்கவும் const userInfo = await commonsApi.get({ action: 'query', meta: 'userinfo', format: 'json' }); if (!userInfo.query || !userInfo.query.userinfo || userInfo.query.userinfo.anon !== undefined) { return { success: false, error: 'பொதுவகத்தில் உள்நுழையவில்லை. தயவுசெய்து முதலில் உள்நுழையவும்.' }; } // 2. FormData ஐ உருவாக்கி, Blob ஐ file புலமாகச் சேர்த்து, filename ஐக் குறிப்பிடவும் const formData = new FormData(); formData.append('action', 'upload'); formData.append('filename', fileName); formData.append('comment', `Tamil audio pronunciation for [[ta:${currentWord}]]`); formData.append('text', `== {{int:filedesc}} ==\n{{Information\n|description={{ta|1=தமிழ்: ${currentWord} என்பதன் ஒலிப்பு}}\n|date=${new Date().toISOString().split('T')[0]}\n|source={{own}}\n|author=[[User:Neyakkoo|Dr. Sathiyaraj Thangasamy]]\n}}\n\n== {{int:license-header}} ==\n{{CC-BY-SA-4.0}}\n\n[[Category:Tamil pronunciation]]`); formData.append('ignorewarnings', '1'); formData.append('format', 'json'); // முக்கியமானது: கோப்பை file புலமாகச் சேர்க்கவும், மூன்றாவது அளவுரு கோப்புப் பெயர் formData.append('file', fileBlob, fileName); // 3. postWithEditToken ஐப் பயன்படுத்தி அனுப்பவும் (FormData ஐ ஆதரிக்கிறது) const res = await commonsApi.postWithEditToken(formData); if (res && res.upload && res.upload.result === 'Success') { return { success: true }; } else { console.error('Commons Upload Error:', res); const errorMsg = res && res.error ? `${res.error.code}: ${res.error.info}` : 'தெரியாத பிழை'; return { success: false, error: errorMsg }; } } catch (e) { console.error('Commons Upload Exception:', e); return { success: false, error: e.message || 'பதிவேற்றத்தில் விதிவிலக்கு ஏற்பட்டது' }; } } async function updateWiktionaryPage(word, fileName, currentContent, wtApi, $btnUI) { const targetAudioTemplate = `{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}`; let newContent = currentContent; newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); if (newContent.includes('=={{மொழி|ta}}==')) { newContent = newContent.replace('=={{மொழி|ta}}==', `=={{மொழி|ta}}==\n\n${targetAudioTemplate}`); } else if (newContent.includes('== {{மொழி|ta}} ==')) { newContent = newContent.replace('== {{மொழி|ta}} ==', `== {{மொழி|ta}} ==\n\n${targetAudioTemplate}`); } else if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* ${targetAudioTemplate}`); } else { newContent = `=={{மொழி|ta}}==\n\n${targetAudioTemplate}\n\n` + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: word, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}` }); if ($btnUI) $btnUI.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); } catch (err) { if ($btnUI) $btnUI.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } } $(document).ready(checkAndLinkAudio); })(); s1flteui9kayhz9nz3djz06futepfod 2004679 2004678 2026-10-26T22:36:20Z Neyakkoo 16078 பயிற்சி 2004679 javascript text/javascript // ============================================================ // Wiktionary Audio Recorder & Linker (Fixed Commons Upload & Auto-Rename) // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); const SCRIPT_RE = /[\u0B80-\u0BFF]/; if (!SCRIPT_RE.test(currentWord)) return; let mediaRecorder, audioChunks = [], recordedBlob = null; async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; if (content.includes('{{ஒலிக்கோப்பு-தமிழ்') || content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு உள்ளது.'); return; } const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } else { showRecordAudioButton(content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', async function () { $(this).prop('disabled', true).text('இணைக்கப்படுகிறது...'); await updateWiktionaryPage(currentWord, fileName, currentContent, wtApi, $(this)); }); $('#firstHeading').append(btn); } function showRecordAudioButton(currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-record-audio-btn', type: 'button', title: 'சொல்லிற்கான புதிய ஒலிப்பை பதிவு செய்' }) .html(`🎙️ <b>ஒலிப்பதிவு செய்</b>`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#d9534f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', function () { openRecorderModal(currentContent, wtApi); }); $('#firstHeading').append(btn); } function openRecorderModal(currentContent, wtApi) { $('#wikt-recorder-modal').remove(); const modalHtml = ` <div id="wikt-recorder-modal" style="position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000; display:flex; align-items:center; justify-content:center;"> <div style="background:#fff; padding:20px; border-radius:8px; width:380px; text-align:center; box-shadow:0 4px 10px rgba(0,0,0,0.3);"> <h3 style="margin-top:0; color:#333;">🎙️ ஒலிப்பதிவு: <b>${currentWord}</b></h3> <p style="font-size:13px; color:#666;">ஒலிவாங்கியைக் கொண்டு சொல்லைத் தெளிவாகப் பேசவும்.</p> <div style="margin:15px 0;"> <button id="btn-start-rec" style="padding:8px 15px; background:#28a745; color:#fff; border:none; border-radius:4px; cursor:pointer;">▶️ பதிவு தொடங்கு</button> <button id="btn-stop-rec" style="padding:8px 15px; background:#dc3545; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">⏹️ நிறுத்து</button> </div> <audio id="rec-preview" controls style="display:none; width:100%; margin-top:10px;"></audio> <div id="rec-status" style="margin-top:10px; font-size:12px; font-weight:bold;"></div> <div style="margin-top:20px; display:flex; justify-content:space-between;"> <button id="btn-cancel-rec" style="padding:6px 12px; background:#6c757d; color:#fff; border:none; border-radius:4px; cursor:pointer;">ரத்து செய்</button> <button id="btn-upload-rec" style="padding:6px 12px; background:#007bff; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">☁️ பொதுவகத்தில் பதிவேற்று</button> </div> </div> </div>`; $('body').append(modalHtml); $('#btn-start-rec').on('click', async function () { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream); audioChunks = []; mediaRecorder.ondataavailable = event => audioChunks.push(event.data); mediaRecorder.onstop = () => { recordedBlob = new Blob(audioChunks, { type: 'audio/ogg' }); const audioUrl = URL.createObjectURL(recordedBlob); $('#rec-preview').attr('src', audioUrl).show(); $('#btn-upload-rec').show(); $('#rec-status').text('ஒலிப்பதிவு முடிந்தது. கேட்டுப் பார்த்துப் பதிவேற்றவும்.').css('color', 'green'); }; mediaRecorder.start(); $(this).hide(); $('#btn-stop-rec').show(); $('#rec-status').text('பதிவாகிறது... பேசுங்கள்!').css('color', 'red'); } catch (err) { alert('ஒலிவாங்கி (Microphone) அனுமதியை வழங்கவும்.'); } }); $('#btn-stop-rec').on('click', function () { mediaRecorder.stop(); $(this).hide(); $('#btn-start-rec').show().text('🔄 மீண்டும் பதிவு செய்'); }); $('#btn-cancel-rec').on('click', function () { $('#wikt-recorder-modal').remove(); }); $('#btn-upload-rec').on('click', async function () { if (!recordedBlob) return; const $btn = $(this); $btn.prop('disabled', true).text('பதிவேற்றப்படுகிறது...'); $('#rec-status').text('விக்கிப் பொதுவகத்தில் பதிவேற்றப்படுகிறது...').css('color', '#007bff'); const fileName = `Ta-${currentWord}.ogg`; const result = await uploadToCommonsFixed(fileName, recordedBlob); if (result.success) { $('#rec-status').text(`பதிவேற்றம் வெற்றி! (${result.fileName}) பக்கத்தில் இணைக்கப்படுகிறது...`).css('color', 'green'); // புதிய கோப்புப் பெயரைப் பயன்படுத்தி இணைக்கவும் await updateWiktionaryPage(currentWord, result.fileName, currentContent, wtApi); $('#wikt-recorder-modal').remove(); } else { $('#rec-status').text(`பதிவேற்றத்தில் பிழை: ${result.error}`).css('color', 'red'); $btn.prop('disabled', false).text('☁️ மீண்டும் முயற்சி செய்'); } }); } // சரிசெய்யப்பட்ட விக்கிப் பொதுவகப் பதிவேற்றச் செயல்பாடு (முன்கூட்டியே சரிபார்த்தல் + தானியங்கி மறுபெயரிடல்) async function uploadToCommonsFixed(fileName, fileBlob) { try { const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); // 1. பொதுவகத்தில் உள்நுழைந்துள்ளீர்களா எனச் சரிபார்க்கவும் const userInfo = await commonsApi.get({ action: 'query', meta: 'userinfo', format: 'json' }); if (!userInfo.query || !userInfo.query.userinfo || userInfo.query.userinfo.anon !== undefined) { return { success: false, error: 'பொதுவகத்தில் உள்நுழையவில்லை. தயவுசெய்து முதலில் உள்நுழையவும்.' }; } // 2. இந்தக் கோப்புப் பெயர் ஏற்கனவே உள்ளதா எனச் சரிபார்க்கவும் let finalFileName = fileName; const checkDup = await commonsApi.get({ action: 'query', titles: `File:${fileName}`, format: 'json' }); // கோப்பு ஏற்கனவே இருந்தால் (pages[-1] இல்லை என்றால்), தானாக மறுபெயரிடவும் if (checkDup.query && checkDup.query.pages && checkDup.query.pages[-1] === undefined) { const timestamp = new Date().getTime(); finalFileName = fileName.replace('.ogg', `_${timestamp}.ogg`); console.log(`கோப்பு ஏற்கனவே உள்ளது, தானாக மறுபெயரிடப்பட்டது: ${finalFileName}`); } // 3. FormData ஐ உருவாக்கி, Blob ஐ file புலமாகச் சேர்த்து, filename ஐக் குறிப்பிடவும் const formData = new FormData(); formData.append('action', 'upload'); formData.append('filename', finalFileName); formData.append('comment', `Tamil audio pronunciation for [[ta:${currentWord}]]`); formData.append('text', `== {{int:filedesc}} ==\n{{Information\n|description={{ta|1=தமிழ்: ${currentWord} என்பதன் ஒலிப்பு}}\n|date=${new Date().toISOString().split('T')[0]}\n|source={{own}}\n|author=[[User:Neyakkoo|Dr. Sathiyaraj Thangasamy]]\n}}\n\n== {{int:license-header}} ==\n{{CC-BY-SA-4.0}}\n\n[[Category:Tamil pronunciation]]`); formData.append('format', 'json'); // முக்கியமானது: கோப்பை file புலமாகச் சேர்க்கவும், மூன்றாவது அளவுரு கோப்புப் பெயர் formData.append('file', fileBlob, finalFileName); // 4. postWithEditToken ஐப் பயன்படுத்தி அனுப்பவும் const res = await commonsApi.postWithEditToken(formData); if (res && res.upload && res.upload.result === 'Success') { return { success: true, fileName: finalFileName }; } else { console.error('Commons Upload Raw Response:', res); let errorMsg = 'தெரியாத பிழை'; if (res && res.error) { errorMsg = `${res.error.code}: ${res.error.info}`; } else if (res && res.upload && res.upload.warnings) { errorMsg = JSON.stringify(res.upload.warnings); } return { success: false, error: errorMsg }; } } catch (e) { console.error('Commons Upload Exception:', e); return { success: false, error: e.message || 'பதிவேற்றத்தில் விதிவிலக்கு ஏற்பட்டது' }; } } async function updateWiktionaryPage(word, fileName, currentContent, wtApi, $btnUI) { const targetAudioTemplate = `{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}`; let newContent = currentContent; newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); if (newContent.includes('=={{மொழி|ta}}==')) { newContent = newContent.replace('=={{மொழி|ta}}==', `=={{மொழி|ta}}==\n\n${targetAudioTemplate}`); } else if (newContent.includes('== {{மொழி|ta}} ==')) { newContent = newContent.replace('== {{மொழி|ta}} ==', `== {{மொழி|ta}} ==\n\n${targetAudioTemplate}`); } else if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* ${targetAudioTemplate}`); } else { newContent = `=={{மொழி|ta}}==\n\n${targetAudioTemplate}\n\n` + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: word, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}` }); if ($btnUI) $btnUI.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); } catch (err) { if ($btnUI) $btnUI.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } } $(document).ready(checkAndLinkAudio); })(); dq8pw2tdend5przvnel0an4l2twivo0 2004680 2004679 2026-10-26T22:42:55Z Neyakkoo 16078 பயிற்சி 2004680 javascript text/javascript // ============================================================ // Wiktionary Audio Recorder & Linker (Spell4Wiki inspired) // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // Optimized with FormData Upload & Auto-Rename // ============================================================ (function () { 'use strict'; if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); const SCRIPT_RE = /[\u0B80-\u0BFF]/; if (!SCRIPT_RE.test(currentWord)) return; let mediaRecorder, audioChunks = [], recordedBlob = null; async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; if (content.includes('{{ஒலிக்கோப்பு-தமிழ்') || content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு உள்ளது.'); return; } const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } else { showRecordAudioButton(content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', async function () { $(this).prop('disabled', true).text('இணைக்கப்படுகிறது...'); await updateWiktionaryPage(currentWord, fileName, currentContent, wtApi, $(this)); }); $('#firstHeading').append(btn); } function showRecordAudioButton(currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-record-audio-btn', type: 'button', title: 'சொல்லிற்கான புதிய ஒலிப்பை பதிவு செய்' }) .html(`🎙️ <b>ஒலிப்பதிவு செய்</b>`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#d9534f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', function () { openRecorderModal(currentContent, wtApi); }); $('#firstHeading').append(btn); } function openRecorderModal(currentContent, wtApi) { $('#wikt-recorder-modal').remove(); const modalHtml = ` <div id="wikt-recorder-modal" style="position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000; display:flex; align-items:center; justify-content:center;"> <div style="background:#fff; padding:20px; border-radius:8px; width:380px; text-align:center; box-shadow:0 4px 10px rgba(0,0,0,0.3);"> <h3 style="margin-top:0; color:#333;">🎙️ ஒலிப்பதிவு: <b>${currentWord}</b></h3> <p style="font-size:13px; color:#666;">ஒலிவாங்கியைக் கொண்டு சொல்லைத் தெளிவாகப் பேசவும்.</p> <div style="margin:15px 0;"> <button id="btn-start-rec" style="padding:8px 15px; background:#28a745; color:#fff; border:none; border-radius:4px; cursor:pointer;">▶️ பதிவு தொடங்கு</button> <button id="btn-stop-rec" style="padding:8px 15px; background:#dc3545; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">⏹️ நிறுத்து</button> </div> <audio id="rec-preview" controls style="display:none; width:100%; margin-top:10px;"></audio> <div id="rec-status" style="margin-top:10px; font-size:12px; font-weight:bold;"></div> <div style="margin-top:20px; display:flex; justify-content:space-between;"> <button id="btn-cancel-rec" style="padding:6px 12px; background:#6c757d; color:#fff; border:none; border-radius:4px; cursor:pointer;">ரத்து செய்</button> <button id="btn-upload-rec" style="padding:6px 12px; background:#007bff; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">☁️ பொதுவகத்தில் பதிவேற்று</button> </div> </div> </div>`; $('body').append(modalHtml); $('#btn-start-rec').on('click', async function () { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream); audioChunks = []; mediaRecorder.ondataavailable = event => audioChunks.push(event.data); mediaRecorder.onstop = () => { recordedBlob = new Blob(audioChunks, { type: 'audio/ogg' }); const audioUrl = URL.createObjectURL(recordedBlob); $('#rec-preview').attr('src', audioUrl).show(); $('#btn-upload-rec').show(); $('#rec-status').text('ஒலிப்பதிவு முடிந்தது. கேட்டுப் பார்த்துப் பதிவேற்றவும்.').css('color', 'green'); }; mediaRecorder.start(); $(this).hide(); $('#btn-stop-rec').show(); $('#rec-status').text('பதிவாகிறது... பேசுங்கள்!').css('color', 'red'); } catch (err) { alert('ஒலிவாங்கி (Microphone) அனுமதியை வழங்கவும்.'); } }); $('#btn-stop-rec').on('click', function () { mediaRecorder.stop(); $(this).hide(); $('#btn-start-rec').show().text('🔄 மீண்டும் பதிவு செய்'); }); $('#btn-cancel-rec').on('click', function () { $('#wikt-recorder-modal').remove(); }); $('#btn-upload-rec').on('click', async function () { if (!recordedBlob) return; const $btn = $(this); $btn.prop('disabled', true).text('பதிவேற்றப்படுகிறது...'); $('#rec-status').text('விக்கிப் பொதுவகத்தில் பதிவேற்றப்படுகிறது...').css('color', '#007bff'); const fileName = `Ta-${currentWord}.ogg`; const result = await uploadToCommons(fileName, recordedBlob); if (result.success) { $('#rec-status').text(`பதிவேற்றம் வெற்றி! (${result.fileName}) பக்கத்தில் இணைக்கப்படுகிறது...`).css('color', 'green'); await updateWiktionaryPage(currentWord, result.fileName, currentContent, wtApi); $('#wikt-recorder-modal').remove(); } else { $('#rec-status').text(`பதிவேற்றத்தில் பிழை: ${result.error}`).css('color', 'red'); $btn.prop('disabled', false).text('☁️ மீண்டும் முயற்சி செய்'); } }); } // மேம்படுத்தப்பட்ட பதிவேற்றுச் செயல்பாடு (FormData + உள்நுழைவு சரிபார்ப்பு + தானியங்கி மறுபெயரிடல்) async function uploadToCommons(fileName, fileBlob) { try { const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); // 1. உள்நுழைவு நிலையைச் சரிபார்க்கவும் const userInfo = await commonsApi.get({ action: 'query', meta: 'userinfo', format: 'json' }); if (!userInfo.query || !userInfo.query.userinfo || userInfo.query.userinfo.anon !== undefined) { return { success: false, error: 'பொதுவகத்தில் உள்நுழையவில்லை. தயவுசெய்து முதலில் உள்நுழையவும்.' }; } // 2. கோப்புப் பெயர் ஏற்கனவே உள்ளதா எனச் சரிபார்த்து, இருந்தால் தானாக மறுபெயரிடவும் let finalFileName = fileName; const checkDup = await commonsApi.get({ action: 'query', titles: `File:${fileName}`, format: 'json' }); if (checkDup.query && checkDup.query.pages && checkDup.query.pages[-1] === undefined) { const timestamp = new Date().getTime(); finalFileName = fileName.replace('.ogg', `_${timestamp}.ogg`); console.log(`கோப்பு ஏற்கனவே உள்ளது, தானாக மறுபெயரிடப்பட்டது: ${finalFileName}`); } // 3. CSRF டோக்கனைப் பெறவும் const tokenData = await commonsApi.get({ action: 'query', meta: 'tokens', format: 'json' }); const csrfToken = tokenData.query.tokens.csrftoken; // 4. FormData ஐ உருவாக்கவும் (Spell4Wiki இன் பதிவேற்று முறையைப் பின்பற்றி) const formData = new FormData(); formData.append('action', 'upload'); formData.append('filename', finalFileName); formData.append('token', csrfToken); formData.append('comment', `Tamil audio pronunciation for [[ta:${currentWord}]]`); formData.append('text', `== {{int:filedesc}} ==\n{{Information\n|description={{ta|1=தமிழ்: ${currentWord} என்பதன் ஒலிப்பு}}\n|date=${new Date().toISOString().split('T')[0]}\n|source={{own}}\n|author=[[User:Neyakkoo|Dr. Sathiyaraj Thangasamy]]\n}}\n\n== {{int:license-header}} ==\n{{CC-BY-SA-4.0}}\n\n[[Category:Tamil pronunciation]]`); formData.append('format', 'json'); // முக்கியமானது: கோப்பை file புலமாகச் சேர்க்கவும் formData.append('file', fileBlob, finalFileName); // 5. fetch ஐப் பயன்படுத்தி நேரடியாகப் பதிவேற்றவும் (CORS ஐ ஆதரிக்கிறது) const response = await fetch('https://commons.wikimedia.org/w/api.php', { method: 'POST', body: formData, credentials: 'include' // உள்நுழைவு 쿠키를 가지고 가져오기 }); const res = await response.json(); if (res && res.upload && res.upload.result === 'Success') { return { success: true, fileName: finalFileName }; } else { console.error('Commons Upload Raw Response:', res); let errorMsg = 'தெரியாத பிழை'; if (res && res.error) { errorMsg = `${res.error.code}: ${res.error.info}`; } return { success: false, error: errorMsg }; } } catch (e) { console.error('Commons Upload Exception:', e); return { success: false, error: e.message || 'பதிவேற்றத்தில் விதிவிலக்கு ஏற்பட்டது' }; } } async function updateWiktionaryPage(word, fileName, currentContent, wtApi, $btnUI) { // ஒலிப்பு வார்ப்புருவை உருவாக்கவும் const targetAudioTemplate = `{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}`; let newContent = currentContent; // பழைய ஒலிப்புப் பதிவுகளை நீக்கவும் newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); if (newContent.includes('=={{மொழி|ta}}==')) { newContent = newContent.replace('=={{மொழி|ta}}==', `=={{மொழி|ta}}==\n\n${targetAudioTemplate}`); } else if (newContent.includes('== {{மொழி|ta}} ==')) { newContent = newContent.replace('== {{மொழி|ta}} ==', `== {{மொழி|ta}} ==\n\n${targetAudioTemplate}`); } else if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* ${targetAudioTemplate}`); } else { newContent = `=={{மொழி|ta}}==\n\n${targetAudioTemplate}\n\n` + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: word, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}` }); if ($btnUI) $btnUI.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); } catch (err) { if ($btnUI) $btnUI.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } } $(document).ready(checkAndLinkAudio); })(); 5aozkfmrlzpy9xd828k5sb7s00qv8o6 2004681 2004680 2026-10-26T22:45:50Z Neyakkoo 16078 பயிற்சி 2004681 javascript text/javascript // ============================================================ // Wiktionary Audio Recorder & Linker (Fixed CORS & Upload) // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); const SCRIPT_RE = /[\u0B80-\u0BFF]/; if (!SCRIPT_RE.test(currentWord)) return; let mediaRecorder, audioChunks = [], recordedBlob = null; async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; if (content.includes('{{ஒலிக்கோப்பு-தமிழ்') || content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு உள்ளது.'); return; } const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } else { showRecordAudioButton(content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', async function () { $(this).prop('disabled', true).text('இணைக்கப்படுகிறது...'); await updateWiktionaryPage(currentWord, fileName, currentContent, wtApi, $(this)); }); $('#firstHeading').append(btn); } function showRecordAudioButton(currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-record-audio-btn', type: 'button', title: 'சொல்லிற்கான புதிய ஒலிப்பை பதிவு செய்' }) .html(`🎙️ <b>ஒலிப்பதிவு செய்</b>`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#d9534f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', function () { openRecorderModal(currentContent, wtApi); }); $('#firstHeading').append(btn); } function openRecorderModal(currentContent, wtApi) { $('#wikt-recorder-modal').remove(); const modalHtml = ` <div id="wikt-recorder-modal" style="position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000; display:flex; align-items:center; justify-content:center;"> <div style="background:#fff; padding:20px; border-radius:8px; width:380px; text-align:center; box-shadow:0 4px 10px rgba(0,0,0,0.3);"> <h3 style="margin-top:0; color:#333;">🎙️ ஒலிப்பதிவு: <b>${currentWord}</b></h3> <p style="font-size:13px; color:#666;">ஒலிவாங்கியைக் கொண்டு சொல்லைத் தெளிவாகப் பேசவும்.</p> <div style="margin:15px 0;"> <button id="btn-start-rec" style="padding:8px 15px; background:#28a745; color:#fff; border:none; border-radius:4px; cursor:pointer;">▶️ பதிவு தொடங்கு</button> <button id="btn-stop-rec" style="padding:8px 15px; background:#dc3545; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">⏹️ நிறுத்து</button> </div> <audio id="rec-preview" controls style="display:none; width:100%; margin-top:10px;"></audio> <div id="rec-status" style="margin-top:10px; font-size:12px; font-weight:bold;"></div> <div style="margin-top:20px; display:flex; justify-content:space-between;"> <button id="btn-cancel-rec" style="padding:6px 12px; background:#6c757d; color:#fff; border:none; border-radius:4px; cursor:pointer;">ரத்து செய்</button> <button id="btn-upload-rec" style="padding:6px 12px; background:#007bff; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">☁️ பொதுவகத்தில் பதிவேற்று</button> </div> </div> </div>`; $('body').append(modalHtml); $('#btn-start-rec').on('click', async function () { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream); audioChunks = []; mediaRecorder.ondataavailable = event => audioChunks.push(event.data); mediaRecorder.onstop = () => { // OGG வடிவத்தில் Blob உருவாக்கம் recordedBlob = new Blob(audioChunks, { type: 'audio/ogg' }); const audioUrl = URL.createObjectURL(recordedBlob); $('#rec-preview').attr('src', audioUrl).show(); $('#btn-upload-rec').show(); $('#rec-status').text('ஒலிப்பதிவு முடிந்தது. கேட்டுப் பார்த்துப் பதிவேற்றவும்.').css('color', 'green'); }; mediaRecorder.start(); $(this).hide(); $('#btn-stop-rec').show(); $('#rec-status').text('பதிவாகிறது... பேசுங்கள்!').css('color', 'red'); } catch (err) { alert('ஒலிவாங்கி (Microphone) அனுமதியை வழங்கவும்.'); } }); $('#btn-stop-rec').on('click', function () { mediaRecorder.stop(); $(this).hide(); $('#btn-start-rec').show().text('🔄 மீண்டும் பதிவு செய்'); }); $('#btn-cancel-rec').on('click', function () { $('#wikt-recorder-modal').remove(); }); $('#btn-upload-rec').on('click', async function () { if (!recordedBlob) return; const $btn = $(this); $btn.prop('disabled', true).text('பதிவேற்றப்படுகிறது...'); $('#rec-status').text('விக்கிப் பொதுவகத்தில் பதிவேற்றப்படுகிறது...').css('color', '#007bff'); const fileName = `Ta-${currentWord}.ogg`; const result = await uploadToCommons(fileName, recordedBlob); if (result.success) { $('#rec-status').text(`பதிவேற்றம் வெற்றி! (${result.fileName}) பக்கத்தில் இணைக்கப்படுகிறது...`).css('color', 'green'); // புதிய கோப்புப் பெயரைப் பயன்படுத்தி இணைக்கவும் await updateWiktionaryPage(currentWord, result.fileName, currentContent, wtApi); $('#wikt-recorder-modal').remove(); } else { $('#rec-status').text(`பதிவேற்றத்தில் பிழை: ${result.error}`).css('color', 'red'); $btn.prop('disabled', false).text('☁️ மீண்டும் முயற்சி செய்'); } }); } // CORS பிழையைத் தவிர்க்க mw.ForeignApi பயன்படுத்தப்படுகிறது async function uploadToCommons(fileName, fileBlob) { try { const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); // 1. உள்நுழைவு நிலையைச் சரிபார்க்கவும் const userInfo = await commonsApi.get({ action: 'query', meta: 'userinfo', format: 'json' }); if (!userInfo.query || !userInfo.query.userinfo || userInfo.query.userinfo.anon !== undefined) { return { success: false, error: 'பொதுவகத்தில் உள்நுழையவில்லை. தயவுசெய்து முதலில் உள்நுழையவும்.' }; } // 2. கோப்புப் பெயர் ஏற்கனவே உள்ளதா எனச் சரிபார்த்து, இருந்தால் தானாக மறுபெயரிடவும் let finalFileName = fileName; const checkDup = await commonsApi.get({ action: 'query', titles: `File:${fileName}`, format: 'json' }); if (checkDup.query && checkDup.query.pages && checkDup.query.pages[-1] === undefined) { const timestamp = new Date().getTime(); finalFileName = fileName.replace('.ogg', `_${timestamp}.ogg`); console.log(`கோப்பு ஏற்கனவே உள்ளது, தானாக மறுபெயரிடப்பட்டது: ${finalFileName}`); } // 3. FormData ஐ உருவாக்கவும் const formData = new FormData(); formData.append('action', 'upload'); formData.append('filename', finalFileName); formData.append('comment', `Tamil audio pronunciation for [[ta:${currentWord}]]`); formData.append('text', `== {{int:filedesc}} ==\n{{Information\n|description={{ta|1=தமிழ்: ${currentWord} என்பதன் ஒலிப்பு}}\n|date=${new Date().toISOString().split('T')[0]}\n|source={{own}}\n|author=[[User:Neyakkoo|Dr. Sathiyaraj Thangasamy]]\n}}\n\n== {{int:license-header}} ==\n{{CC-BY-SA-4.0}}\n\n[[Category:Tamil pronunciation]]`); formData.append('format', 'json'); // முக்கியமானது: கோப்பை file புலமாகச் சேர்க்கவும் formData.append('file', fileBlob, finalFileName); // 4. postWithEditToken மூலம் பதிவேற்றவும் (இது தானாகவே CSRF டோக்கனைச் சேர்த்து CORS ஐக் கையாளும்) const res = await commonsApi.postWithEditToken(formData); if (res && res.upload && res.upload.result === 'Success') { return { success: true, fileName: finalFileName }; } else { console.error('Commons Upload Raw Response:', res); let errorMsg = 'தெரியாத பிழை'; if (res && res.error) { errorMsg = `${res.error.code}: ${res.error.info}`; } return { success: false, error: errorMsg }; } } catch (e) { console.error('Commons Upload Exception:', e); return { success: false, error: e.message || 'பதிவேற்றத்தில் விதிவிலக்கு ஏற்பட்டது' }; } } async function updateWiktionaryPage(word, fileName, currentContent, wtApi, $btnUI) { const targetAudioTemplate = `{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}`; let newContent = currentContent; // பழைய ஒலிப்புப் பதிவுகளை நீக்கவும் newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); if (newContent.includes('=={{மொழி|ta}}==')) { newContent = newContent.replace('=={{மொழி|ta}}==', `=={{மொழி|ta}}==\n\n${targetAudioTemplate}`); } else if (newContent.includes('== {{மொழி|ta}} ==')) { newContent = newContent.replace('== {{மொழி|ta}} ==', `== {{மொழி|ta}} ==\n\n${targetAudioTemplate}`); } else if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* ${targetAudioTemplate}`); } else { newContent = `=={{மொழி|ta}}==\n\n${targetAudioTemplate}\n\n` + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: word, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}` }); if ($btnUI) $btnUI.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); } catch (err) { if ($btnUI) $btnUI.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } } $(document).ready(checkAndLinkAudio); })(); rlvon76psjmr6gfeljvgaag6uz7ngv7 2004682 2004681 2026-10-26T22:48:00Z Neyakkoo 16078 பயிற்சி 2004682 javascript text/javascript // ============================================================ // Wiktionary Audio Recorder & Linker (Fixed CORS, Upload & Auto-Rename) // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); const SCRIPT_RE = /[\u0B80-\u0BFF]/; if (!SCRIPT_RE.test(currentWord)) return; let mediaRecorder, audioChunks = [], recordedBlob = null; async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; if (content.includes('{{ஒலிக்கோப்பு-தமிழ்') || content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு உள்ளது.'); return; } const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } else { showRecordAudioButton(content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', async function () { $(this).prop('disabled', true).text('இணைக்கப்படுகிறது...'); await updateWiktionaryPage(currentWord, fileName, currentContent, wtApi, $(this)); }); $('#firstHeading').append(btn); } function showRecordAudioButton(currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-record-audio-btn', type: 'button', title: 'சொல்லிற்கான புதிய ஒலிப்பை பதிவு செய்' }) .html(`🎙️ <b>ஒலிப்பதிவு செய்</b>`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#d9534f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', function () { openRecorderModal(currentContent, wtApi); }); $('#firstHeading').append(btn); } function openRecorderModal(currentContent, wtApi) { $('#wikt-recorder-modal').remove(); const modalHtml = ` <div id="wikt-recorder-modal" style="position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000; display:flex; align-items:center; justify-content:center;"> <div style="background:#fff; padding:20px; border-radius:8px; width:380px; text-align:center; box-shadow:0 4px 10px rgba(0,0,0,0.3);"> <h3 style="margin-top:0; color:#333;">🎙️ ஒலிப்பதிவு: <b>${currentWord}</b></h3> <p style="font-size:13px; color:#666;">ஒலிவாங்கியைக் கொண்டு சொல்லைத் தெளிவாகப் பேசவும்.</p> <div style="margin:15px 0;"> <button id="btn-start-rec" style="padding:8px 15px; background:#28a745; color:#fff; border:none; border-radius:4px; cursor:pointer;">▶️ பதிவு தொடங்கு</button> <button id="btn-stop-rec" style="padding:8px 15px; background:#dc3545; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">⏹️ நிறுத்து</button> </div> <audio id="rec-preview" controls style="display:none; width:100%; margin-top:10px;"></audio> <div id="rec-status" style="margin-top:10px; font-size:12px; font-weight:bold;"></div> <div style="margin-top:20px; display:flex; justify-content:space-between;"> <button id="btn-cancel-rec" style="padding:6px 12px; background:#6c757d; color:#fff; border:none; border-radius:4px; cursor:pointer;">ரத்து செய்</button> <button id="btn-upload-rec" style="padding:6px 12px; background:#007bff; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">☁️ பொதுவகத்தில் பதிவேற்று</button> </div> </div> </div>`; $('body').append(modalHtml); $('#btn-start-rec').on('click', async function () { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream); audioChunks = []; mediaRecorder.ondataavailable = event => audioChunks.push(event.data); mediaRecorder.onstop = () => { recordedBlob = new Blob(audioChunks, { type: 'audio/ogg' }); const audioUrl = URL.createObjectURL(recordedBlob); $('#rec-preview').attr('src', audioUrl).show(); $('#btn-upload-rec').show(); $('#rec-status').text('ஒலிப்பதிவு முடிந்தது. கேட்டுப் பார்த்துப் பதிவேற்றவும்.').css('color', 'green'); }; mediaRecorder.start(); $(this).hide(); $('#btn-stop-rec').show(); $('#rec-status').text('பதிவாகிறது... பேசுங்கள்!').css('color', 'red'); } catch (err) { alert('ஒலிவாங்கி (Microphone) அனுமதியை வழங்கவும்.'); } }); $('#btn-stop-rec').on('click', function () { mediaRecorder.stop(); $(this).hide(); $('#btn-start-rec').show().text('🔄 மீண்டும் பதிவு செய்'); }); $('#btn-cancel-rec').on('click', function () { $('#wikt-recorder-modal').remove(); }); $('#btn-upload-rec').on('click', async function () { if (!recordedBlob) return; const $btn = $(this); $btn.prop('disabled', true).text('பதிவேற்றப்படுகிறது...'); $('#rec-status').text('விக்கிப் பொதுவகத்தில் பதிவேற்றப்படுகிறது...').css('color', '#007bff'); const fileName = `Ta-${currentWord}.ogg`; const result = await uploadToCommons(fileName, recordedBlob); if (result.success) { $('#rec-status').text(`பதிவேற்றம் வெற்றி! (${result.fileName}) பக்கத்தில் இணைக்கப்படுகிறது...`).css('color', 'green'); await updateWiktionaryPage(currentWord, result.fileName, currentContent, wtApi); $('#wikt-recorder-modal').remove(); } else { $('#rec-status').text(`பதிவேற்றத்தில் பிழை: ${result.error}`).css('color', 'red'); $btn.prop('disabled', false).text('☁️ மீண்டும் முயற்சி செய்'); } }); } // CORS பிழை மற்றும் பிழையான பதிவேற்றங்களைத் தடுக்க fetch + FormData + origin பயன்படுத்தப்படுகிறது async function uploadToCommons(fileName, fileBlob) { try { const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); // 1. உள்நுழைவு நிலையைச் சரிபார்க்கவும் const userInfo = await commonsApi.get({ action: 'query', meta: 'userinfo', format: 'json' }); if (!userInfo.query || !userInfo.query.userinfo || userInfo.query.userinfo.anon !== undefined) { return { success: false, error: 'பொதுவகத்தில் உள்நுழையவில்லை. தயவுசெய்து முதலில் உள்நுழையவும்.' }; } // 2. CSRF டோக்கனைப் பெறவும் const tokenData = await commonsApi.get({ action: 'query', meta: 'tokens', format: 'json' }); const csrfToken = tokenData.query.tokens.csrftoken; // 3. கோப்புப் பெயர் ஏற்கனவே உள்ளதா எனச் சரிபார்த்து, இருந்தால் தானாக மறுபெயரிடவும் let finalFileName = fileName; const checkDup = await commonsApi.get({ action: 'query', titles: `File:${fileName}`, format: 'json' }); if (checkDup.query && checkDup.query.pages && checkDup.query.pages[-1] === undefined) { const timestamp = new Date().getTime(); finalFileName = fileName.replace('.ogg', `_${timestamp}.ogg`); console.log(`கோப்பு ஏற்கனவே உள்ளது, தானாக மறுபெயரிடப்பட்டது: ${finalFileName}`); } // 4. FormData ஐ உருவாக்கவும் const formData = new FormData(); formData.append('action', 'upload'); formData.append('filename', finalFileName); formData.append('token', csrfToken); formData.append('comment', `Tamil audio pronunciation for [[ta:${currentWord}]]`); formData.append('text', `== {{int:filedesc}} ==\n{{Information\n|description={{ta|1=தமிழ்: ${currentWord} என்பதன் ஒலிப்பு}}\n|date=${new Date().toISOString().split('T')[0]}\n|source={{own}}\n|author=[[User:Neyakkoo|Dr. Sathiyaraj Thangasamy]]\n}}\n\n== {{int:license-header}} ==\n{{CC-BY-SA-4.0}}\n\n[[Category:Tamil pronunciation]]`); formData.append('format', 'json'); formData.append('file', fileBlob, finalFileName); // 5. fetch ஐப் பயன்படுத்தி நேரடியாக அனுப்பவும், origin அளவுருவைச் சேர்க்கவும் (CORS ஐ அனுமதிக்க) const url = `https://commons.wikimedia.org/w/api.php?origin=${encodeURIComponent(location.origin)}`; const response = await fetch(url, { method: 'POST', body: formData, credentials: 'include' // உள்நுழைவு 쿠키를 가지고 가져오기 }); const res = await response.json(); if (res && res.upload && res.upload.result === 'Success') { return { success: true, fileName: finalFileName }; } else { console.error('Commons Upload Raw Response:', res); let errorMsg = 'தெரியாத பிழை'; if (res && res.error) { errorMsg = `${res.error.code}: ${res.error.info}`; } else if (res && res.upload && res.upload.warnings) { errorMsg = JSON.stringify(res.upload.warnings); } return { success: false, error: errorMsg }; } } catch (e) { console.error('Commons Upload Exception:', e); return { success: false, error: e.message || 'பதிவேற்றத்தில் விதிவிலக்கு ஏற்பட்டது' }; } } async function updateWiktionaryPage(word, fileName, currentContent, wtApi, $btnUI) { const targetAudioTemplate = `{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}`; let newContent = currentContent; // பழைய ஒலிப்புப் பதிவுகளை நீக்கவும் newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); if (newContent.includes('=={{மொழி|ta}}==')) { newContent = newContent.replace('=={{மொழி|ta}}==', `=={{மொழி|ta}}==\n\n${targetAudioTemplate}`); } else if (newContent.includes('== {{மொழி|ta}} ==')) { newContent = newContent.replace('== {{மொழி|ta}} ==', `== {{மொழி|ta}} ==\n\n${targetAudioTemplate}`); } else if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* ${targetAudioTemplate}`); } else { newContent = `=={{மொழி|ta}}==\n\n${targetAudioTemplate}\n\n` + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: word, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}` }); if ($btnUI) $btnUI.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); } catch (err) { if ($btnUI) $btnUI.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } } $(document).ready(checkAndLinkAudio); })(); d0padpvsqgobvqvd0fm61elarrufvtr 2004683 2004682 2026-10-26T22:49:51Z Neyakkoo 16078 பயிற்சி 2004683 javascript text/javascript // ============================================================ // Wiktionary Audio Recorder & Linker (Fixed MIME Type Error) // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); const SCRIPT_RE = /[\u0B80-\u0BFF]/; if (!SCRIPT_RE.test(currentWord)) return; let mediaRecorder, audioChunks = [], recordedBlob = null; let selectedMimeType = 'audio/ogg'; // இயல்புநிலை let selectedExtension = '.ogg'; async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; if (content.includes('{{ஒலிக்கோப்பு-தமிழ்') || content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு உள்ளது.'); return; } const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } else { showRecordAudioButton(content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', async function () { $(this).prop('disabled', true).text('இணைக்கப்படுகிறது...'); await updateWiktionaryPage(currentWord, fileName, currentContent, wtApi, $(this)); }); $('#firstHeading').append(btn); } function showRecordAudioButton(currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-record-audio-btn', type: 'button', title: 'சொல்லிற்கான புதிய ஒலிப்பை பதிவு செய்' }) .html(`🎙️ <b>ஒலிப்பதிவு செய்</b>`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#d9534f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', function () { openRecorderModal(currentContent, wtApi); }); $('#firstHeading').append(btn); } function openRecorderModal(currentContent, wtApi) { $('#wikt-recorder-modal').remove(); const modalHtml = ` <div id="wikt-recorder-modal" style="position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000; display:flex; align-items:center; justify-content:center;"> <div style="background:#fff; padding:20px; border-radius:8px; width:380px; text-align:center; box-shadow:0 4px 10px rgba(0,0,0,0.3);"> <h3 style="margin-top:0; color:#333;">🎙️ ஒலிப்பதிவு: <b>${currentWord}</b></h3> <p style="font-size:13px; color:#666;">ஒலிவாங்கியைக் கொண்டு சொல்லைத் தெளிவாகப் பேசவும்.</p> <div style="margin:15px 0;"> <button id="btn-start-rec" style="padding:8px 15px; background:#28a745; color:#fff; border:none; border-radius:4px; cursor:pointer;">▶️ பதிவு தொடங்கு</button> <button id="btn-stop-rec" style="padding:8px 15px; background:#dc3545; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">⏹️ நிறுத்து</button> </div> <audio id="rec-preview" controls style="display:none; width:100%; margin-top:10px;"></audio> <div id="rec-status" style="margin-top:10px; font-size:12px; font-weight:bold;"></div> <div style="margin-top:20px; display:flex; justify-content:space-between;"> <button id="btn-cancel-rec" style="padding:6px 12px; background:#6c757d; color:#fff; border:none; border-radius:4px; cursor:pointer;">ரத்து செய்</button> <button id="btn-upload-rec" style="padding:6px 12px; background:#007bff; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">☁️ பொதுவகத்தில் பதிவேற்று</button> </div> </div> </div>`; $('body').append(modalHtml); $('#btn-start-rec').on('click', async function () { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // உலாவியின் ஆதரவைச் சரிபார்க்கவும் if (MediaRecorder.isTypeSupported('audio/ogg; codecs=opus') || MediaRecorder.isTypeSupported('audio/ogg')) { selectedMimeType = 'audio/ogg'; selectedExtension = '.ogg'; } else { // உலாவி OGG-ஐ ஆதரிக்கவில்லை என்றால் WebM பயன்படுத்தவும் selectedMimeType = 'audio/webm'; selectedExtension = '.webm'; } mediaRecorder = new MediaRecorder(stream, { mimeType: selectedMimeType }); audioChunks = []; mediaRecorder.ondataavailable = event => audioChunks.push(event.data); mediaRecorder.onstop = () => { recordedBlob = new Blob(audioChunks, { type: selectedMimeType }); const audioUrl = URL.createObjectURL(recordedBlob); $('#rec-preview').attr('src', audioUrl).show(); $('#btn-upload-rec').show(); $('#rec-status').text(`ஒலிப்பதிவு முடிந்தது (${selectedMimeType}). கேட்டுப் பார்த்துப் பதிவேற்றவும்.`).css('color', 'green'); }; mediaRecorder.start(); $(this).hide(); $('#btn-stop-rec').show(); $('#rec-status').text('பதிவாகிறது... பேசுங்கள்!').css('color', 'red'); } catch (err) { alert('ஒலிவாங்கி (Microphone) அனுமதியை வழங்கவும்.'); console.error(err); } }); $('#btn-stop-rec').on('click', function () { mediaRecorder.stop(); $(this).hide(); $('#btn-start-rec').show().text('🔄 மீண்டும் பதிவு செய்'); }); $('#btn-cancel-rec').on('click', function () { $('#wikt-recorder-modal').remove(); }); $('#btn-upload-rec').on('click', async function () { if (!recordedBlob) return; const $btn = $(this); $btn.prop('disabled', true).text('பதிவேற்றப்படுகிறது...'); $('#rec-status').text('விக்கிப் பொதுவகத்தில் பதிவேற்றப்படுகிறது...').css('color', '#007bff'); // தேர்ந்தெடுக்கப்பட்ட நீட்டிப்பைப் பயன்படுத்தவும் const fileName = `Ta-${currentWord}${selectedExtension}`; const result = await uploadToCommons(fileName, recordedBlob); if (result.success) { $('#rec-status').text(`பதிவேற்றம் வெற்றி! (${result.fileName}) பக்கத்தில் இணைக்கப்படுகிறது...`).css('color', 'green'); await updateWiktionaryPage(currentWord, result.fileName, currentContent, wtApi); $('#wikt-recorder-modal').remove(); } else { $('#rec-status').text(`பதிவேற்றத்தில் பிழை: ${result.error}`).css('color', 'red'); $btn.prop('disabled', false).text('☁️ மீண்டும் முயற்சி செய்'); } }); } async function uploadToCommons(fileName, fileBlob) { try { const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); // 1. உள்நுழைவு நிலையைச் சரிபார்க்கவும் const userInfo = await commonsApi.get({ action: 'query', meta: 'userinfo', format: 'json' }); if (!userInfo.query || !userInfo.query.userinfo || userInfo.query.userinfo.anon !== undefined) { return { success: false, error: 'பொதுவகத்தில் உள்நுழையவில்லை. தயவுசெய்து முதலில் உள்நுழையவும்.' }; } // 2. CSRF டோக்கனைப் பெறவும் const tokenData = await commonsApi.get({ action: 'query', meta: 'tokens', format: 'json' }); const csrfToken = tokenData.query.tokens.csrftoken; // 3. கோப்புப் பெயர் ஏற்கனவே உள்ளதா எனச் சரிபார்த்து, இருந்தால் தானாக மறுபெயரிடவும் let finalFileName = fileName; const checkDup = await commonsApi.get({ action: 'query', titles: `File:${fileName}`, format: 'json' }); if (checkDup.query && checkDup.query.pages && checkDup.query.pages[-1] === undefined) { const timestamp = new Date().getTime(); const ext = fileName.substring(fileName.lastIndexOf('.')); finalFileName = fileName.replace(ext, `_${timestamp}${ext}`); console.log(`கோப்பு ஏற்கனவே உள்ளது, தானாக மறுபெயரிடப்பட்டது: ${finalFileName}`); } // 4. FormData ஐ உருவாக்கவும் const formData = new FormData(); formData.append('action', 'upload'); formData.append('filename', finalFileName); formData.append('token', csrfToken); formData.append('comment', `Tamil audio pronunciation for [[ta:${currentWord}]]`); formData.append('text', `== {{int:filedesc}} ==\n{{Information\n|description={{ta|1=தமிழ்: ${currentWord} என்பதன் ஒலிப்பு}}\n|date=${new Date().toISOString().split('T')[0]}\n|source={{own}}\n|author=[[User:Neyakkoo|Dr. Sathiyaraj Thangasamy]]\n}}\n\n== {{int:license-header}} ==\n{{CC-BY-SA-4.0}}\n\n[[Category:Tamil pronunciation]]`); formData.append('format', 'json'); formData.append('file', fileBlob, finalFileName); // 5. fetch ஐப் பயன்படுத்தி நேரடியாக அனுப்பவும், origin அளவுருவைச் சேர்க்கவும் (CORS ஐ அனுமதிக்க) const url = `https://commons.wikimedia.org/w/api.php?origin=${encodeURIComponent(location.origin)}`; const response = await fetch(url, { method: 'POST', body: formData, credentials: 'include' // உள்நுழைவு 쿠키를 가지고 가져오기 }); const res = await response.json(); if (res && res.upload && res.upload.result === 'Success') { return { success: true, fileName: finalFileName }; } else { console.error('Commons Upload Raw Response:', res); let errorMsg = 'தெரியாத பிழை'; if (res && res.error) { errorMsg = `${res.error.code}: ${res.error.info}`; } else if (res && res.upload && res.upload.warnings) { errorMsg = JSON.stringify(res.upload.warnings); } return { success: false, error: errorMsg }; } } catch (e) { console.error('Commons Upload Exception:', e); return { success: false, error: e.message || 'பதிவேற்றத்தில் விதிவிலக்கு ஏற்பட்டது' }; } } async function updateWiktionaryPage(word, fileName, currentContent, wtApi, $btnUI) { const targetAudioTemplate = `{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}`; let newContent = currentContent; // பழைய ஒலிப்புப் பதிவுகளை நீக்கவும் newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); if (newContent.includes('=={{மொழி|ta}}==')) { newContent = newContent.replace('=={{மொழி|ta}}==', `=={{மொழி|ta}}==\n\n${targetAudioTemplate}`); } else if (newContent.includes('== {{மொழி|ta}} ==')) { newContent = newContent.replace('== {{மொழி|ta}} ==', `== {{மொழி|ta}} ==\n\n${targetAudioTemplate}`); } else if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* ${targetAudioTemplate}`); } else { newContent = `=={{மொழி|ta}}==\n\n${targetAudioTemplate}\n\n` + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: word, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}` }); if ($btnUI) $btnUI.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); } catch (err) { if ($btnUI) $btnUI.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } } $(document).ready(checkAndLinkAudio); })(); mz5qka4a1o3pwisu80m8yftfcovcp0k 2004685 2004683 2026-10-26T22:58:18Z Neyakkoo 16078 பயிற்சி 2004685 javascript text/javascript // ============================================================ // Wiktionary Audio Recorder & Linker (with WebM to OGG Conversion) // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); const SCRIPT_RE = /[\u0B80-\u0BFF]/; if (!SCRIPT_RE.test(currentWord)) return; let mediaRecorder, audioChunks = [], recordedBlob = null; let ffmpegLoaded = false; let ffmpegInstance = null; // FFmpeg.wasm ஐ மட்டும் தேவைப்படும்போது ஏற்றவும் async function loadFFmpeg() { if (ffmpegLoaded) return; const { createFFmpeg } = FFmpeg; ffmpegInstance = createFFmpeg({ log: false }); // true எனில் கன்சோலில் விவரங்கள் காட்டும் await ffmpegInstance.load(); ffmpegLoaded = true; } async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; if (content.includes('{{ஒலிக்கோப்பு-தமிழ்') || content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு உள்ளது.'); return; } const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } else { showRecordAudioButton(content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', async function () { $(this).prop('disabled', true).text('இணைக்கப்படுகிறது...'); await updateWiktionaryPage(currentWord, fileName, currentContent, wtApi, $(this)); }); $('#firstHeading').append(btn); } function showRecordAudioButton(currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-record-audio-btn', type: 'button', title: 'சொல்லிற்கான புதிய ஒலிப்பை பதிவு செய்' }) .html(`🎙️ <b>ஒலிப்பதிவு செய்</b>`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#d9534f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', function () { openRecorderModal(currentContent, wtApi); }); $('#firstHeading').append(btn); } function openRecorderModal(currentContent, wtApi) { $('#wikt-recorder-modal').remove(); const modalHtml = ` <div id="wikt-recorder-modal" style="position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000; display:flex; align-items:center; justify-content:center;"> <div style="background:#fff; padding:20px; border-radius:8px; width:380px; text-align:center; box-shadow:0 4px 10px rgba(0,0,0,0.3);"> <h3 style="margin-top:0; color:#333;">🎙️ ஒலிப்பதிவு: <b>${currentWord}</b></h3> <p style="font-size:13px; color:#666;">ஒலிவாங்கியைக் கொண்டு சொல்லைத் தெளிவாகப் பேசவும்.</p> <div style="margin:15px 0;"> <button id="btn-start-rec" style="padding:8px 15px; background:#28a745; color:#fff; border:none; border-radius:4px; cursor:pointer;">▶️ பதிவு தொடங்கு</button> <button id="btn-stop-rec" style="padding:8px 15px; background:#dc3545; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">⏹️ நிறுத்து</button> </div> <audio id="rec-preview" controls style="display:none; width:100%; margin-top:10px;"></audio> <div id="rec-status" style="margin-top:10px; font-size:12px; font-weight:bold;"></div> <div style="margin-top:20px; display:flex; justify-content:space-between;"> <button id="btn-cancel-rec" style="padding:6px 12px; background:#6c757d; color:#fff; border:none; border-radius:4px; cursor:pointer;">ரத்து செய்</button> <button id="btn-upload-rec" style="padding:6px 12px; background:#007bff; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">☁️ பொதுவகத்தில் பதிவேற்று</button> </div> </div> </div>`; $('body').append(modalHtml); $('#btn-start-rec').on('click', async function () { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // Chrome/Edge-ல் webm மட்டுமே ஆதரவு. Firefox-ல் ogg ஆதரவு இருக்கலாம். const mimeType = MediaRecorder.isTypeSupported('audio/webm') ? 'audio/webm' : 'audio/ogg'; mediaRecorder = new MediaRecorder(stream, { mimeType: mimeType }); audioChunks = []; mediaRecorder.ondataavailable = event => audioChunks.push(event.data); mediaRecorder.onstop = async () => { const rawBlob = new Blob(audioChunks, { type: mediaRecorder.mimeType }); // webm என்றால் OGG ஆக மாற்ற வேண்டும் if (mediaRecorder.mimeType.includes('webm')) { $('#rec-status').text('OGG வடிவத்திற்கு மாற்றப்படுகிறது (சிறிது நேரம் ஆகலாம்)...').css('color', '#007bff'); $('#btn-upload-rec').prop('disabled', true).hide(); try { await loadFFmpeg(); const arrayBuffer = await rawBlob.arrayBuffer(); const uint8Array = new Uint8Array(arrayBuffer); ffmpegInstance.FS('writeFile', 'input.webm', uint8Array); // libvorbis குறியாக்கியைப் பயன்படுத்தி OGG ஆக மாற்றவும் await ffmpegInstance.run('-i', 'input.webm', '-acodec', 'libvorbis', '-q:a', '4', 'output.ogg'); const oggData = ffmpegInstance.FS('readFile', 'output.ogg'); recordedBlob = new Blob([oggData.buffer], { type: 'audio/ogg' }); // மாற்றப்பட்ட கோப்பை முன்னோட்டமாகக் காட்டவும் const audioUrl = URL.createObjectURL(recordedBlob); $('#rec-preview').attr('src', audioUrl).show(); $('#btn-upload-rec').prop('disabled', false).show(); $('#rec-status').text('OGG வடிவத்திற்கு மாற்றம் வெற்றி! கேட்டுப் பார்த்துப் பதிவேற்றவும்.').css('color', 'green'); } catch (err) { console.error("FFmpeg conversion error:", err); $('#rec-status').text('OGG ஆக மாற்றுவதில் பிழை. மீண்டும் முயற்சிக்கவும்.').css('color', 'red'); $('#btn-upload-rec').prop('disabled', false).show(); } } else { // ஏற்கனவே ogg என்றால் நேரடியாகப் பயன்படுத்தவும் recordedBlob = rawBlob; const audioUrl = URL.createObjectURL(recordedBlob); $('#rec-preview').attr('src', audioUrl).show(); $('#btn-upload-rec').show(); $('#rec-status').text('ஒலிப்பதிவு முடிந்தது. கேட்டுப் பார்த்துப் பதிவேற்றவும்.').css('color', 'green'); } }; mediaRecorder.start(); $(this).hide(); $('#btn-stop-rec').show(); $('#rec-status').text('பதிவாகிறது... பேசுங்கள்!').css('color', 'red'); } catch (err) { alert('ஒலிவாங்கி (Microphone) அனுமதியை வழங்கவும்.'); } }); $('#btn-stop-rec').on('click', function () { mediaRecorder.stop(); $(this).hide(); $('#btn-start-rec').show().text('🔄 மீண்டும் பதிவு செய்'); }); $('#btn-cancel-rec').on('click', function () { $('#wikt-recorder-modal').remove(); }); $('#btn-upload-rec').on('click', async function () { if (!recordedBlob) return; const $btn = $(this); $btn.prop('disabled', true).text('பதிவேற்றப்படுகிறது...'); $('#rec-status').text('விக்கிப் பொதுவகத்தில் பதிவேற்றப்படுகிறது...').css('color', '#007bff'); // கோப்பு பெயர் எப்போதும் .ogg ஆக இருக்க வேண்டும் const fileName = `Ta-${currentWord}.ogg`; const result = await uploadToCommons(fileName, recordedBlob); if (result.success) { $('#rec-status').text(`பதிவேற்றம் வெற்றி! (${result.fileName}) பக்கத்தில் இணைக்கப்படுகிறது...`).css('color', 'green'); await updateWiktionaryPage(currentWord, result.fileName, currentContent, wtApi); $('#wikt-recorder-modal').remove(); } else { $('#rec-status').text(`பதிவேற்றத்தில் பிழை: ${result.error}`).css('color', 'red'); $btn.prop('disabled', false).text('☁️ மீண்டும் முயற்சி செய்'); } }); } async function uploadToCommons(fileName, fileBlob) { try { const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); // 1. உள்நுழைவு நிலையைச் சரிபார்க்கவும் const userInfo = await commonsApi.get({ action: 'query', meta: 'userinfo', format: 'json' }); if (!userInfo.query || !userInfo.query.userinfo || userInfo.query.userinfo.anon !== undefined) { return { success: false, error: 'பொதுவகத்தில் உள்நுழையவில்லை. தயவுசெய்து முதலில் உள்நுழையவும்.' }; } // 2. CSRF டோக்கனைப் பெறவும் const tokenData = await commonsApi.get({ action: 'query', meta: 'tokens', format: 'json' }); const csrfToken = tokenData.query.tokens.csrftoken; // 3. கோப்புப் பெயர் ஏற்கனவே உள்ளதா எனச் சரிபார்த்து, இருந்தால் தானாக மறுபெயரிடவும் let finalFileName = fileName; const checkDup = await commonsApi.get({ action: 'query', titles: `File:${fileName}`, format: 'json' }); if (checkDup.query && checkDup.query.pages && checkDup.query.pages[-1] === undefined) { const timestamp = new Date().getTime(); finalFileName = fileName.replace('.ogg', `_${timestamp}.ogg`); console.log(`கோப்பு ஏற்கனவே உள்ளது, தானாக மறுபெயரிடப்பட்டது: ${finalFileName}`); } // 4. FormData ஐ உருவாக்கவும் const formData = new FormData(); formData.append('action', 'upload'); formData.append('filename', finalFileName); formData.append('token', csrfToken); formData.append('comment', `Tamil audio pronunciation for [[ta:${currentWord}]]`); formData.append('text', `== {{int:filedesc}} ==\n{{Information\n|description={{ta|1=தமிழ்: ${currentWord} என்பதன் ஒலிப்பு}}\n|date=${new Date().toISOString().split('T')[0]}\n|source={{own}}\n|author=[[User:Neyakkoo|Dr. Sathiyaraj Thangasamy]]\n}}\n\n== {{int:license-header}} ==\n{{CC-BY-SA-4.0}}\n\n[[Category:Tamil pronunciation]]`); formData.append('format', 'json'); formData.append('file', fileBlob, finalFileName); // 5. fetch ஐப் பயன்படுத்தி நேரடியாக அனுப்பவும், origin அளவுருவைச் சேர்க்கவும் const url = `https://commons.wikimedia.org/w/api.php?origin=${encodeURIComponent(location.origin)}`; const response = await fetch(url, { method: 'POST', body: formData, credentials: 'include' }); const res = await response.json(); if (res && res.upload && res.upload.result === 'Success') { return { success: true, fileName: finalFileName }; } else { console.error('Commons Upload Raw Response:', res); let errorMsg = 'தெரியாத பிழை'; if (res && res.error) { errorMsg = `${res.error.code}: ${res.error.info}`; } else if (res && res.upload && res.upload.warnings) { errorMsg = JSON.stringify(res.upload.warnings); } return { success: false, error: errorMsg }; } } catch (e) { console.error('Commons Upload Exception:', e); return { success: false, error: e.message || 'பதிவேற்றத்தில் விதிவிலக்கு ஏற்பட்டது' }; } } async function updateWiktionaryPage(word, fileName, currentContent, wtApi, $btnUI) { // ஒலிப்பு வார்ப்புருவை உருவாக்கவும் const targetAudioTemplate = `{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}`; let newContent = currentContent; // பழைய ஒலிப்புப் பதிவுகளை நீக்கவும் newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); if (newContent.includes('=={{மொழி|ta}}==')) { newContent = newContent.replace('=={{மொழி|ta}}==', `=={{மொழி|ta}}==\n\n${targetAudioTemplate}`); } else if (newContent.includes('== {{மொழி|ta}} ==')) { newContent = newContent.replace('== {{மொழி|ta}} ==', `== {{மொழி|ta}} ==\n\n${targetAudioTemplate}`); } else if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* ${targetAudioTemplate}`); } else { newContent = `=={{மொழி|ta}}==\n\n${targetAudioTemplate}\n\n` + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: word, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}` }); if ($btnUI) $btnUI.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); } catch (err) { if ($btnUI) $btnUI.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } } $(document).ready(checkAndLinkAudio); })(); bhwnt9o7gkj3esysro58ma8u87ewr0k 2004688 2004685 2026-10-26T23:02:13Z Neyakkoo 16078 - 2004688 javascript text/javascript // ============================================================ // Wiktionary Audio Recorder & Linker (with WebM to OGG Conversion) // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); const SCRIPT_RE = /[\u0B80-\u0BFF]/; if (!SCRIPT_RE.test(currentWord)) return; let mediaRecorder, audioChunks = [], recordedBlob = null; let ffmpegLoaded = false; let ffmpegInstance = null; // FFmpeg.wasm ஐ மட்டும் தேவைப்படும்போது ஏற்றவும் async function loadFFmpeg() { if (ffmpegLoaded) return; const { createFFmpeg } = FFmpeg; ffmpegInstance = createFFmpeg({ log: false }); // true எனில் கன்சோலில் விவரங்கள் காட்டும் await ffmpegInstance.load(); ffmpegLoaded = true; } async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; if (content.includes('{{ஒலிக்கோப்பு-தமிழ்') || content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு உள்ளது.'); return; } const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } else { showRecordAudioButton(content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', async function () { $(this).prop('disabled', true).text('இணைக்கப்படுகிறது...'); await updateWiktionaryPage(currentWord, fileName, currentContent, wtApi, $(this)); }); $('#firstHeading').append(btn); } function showRecordAudioButton(currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-record-audio-btn', type: 'button', title: 'சொல்லிற்கான புதிய ஒலிப்பை பதிவு செய்' }) .html(`🎙️ <b>ஒலிப்பதிவு செய்</b>`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#d9534f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', function () { openRecorderModal(currentContent, wtApi); }); $('#firstHeading').append(btn); } function openRecorderModal(currentContent, wtApi) { $('#wikt-recorder-modal').remove(); const modalHtml = ` <div id="wikt-recorder-modal" style="position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000; display:flex; align-items:center; justify-content:center;"> <div style="background:#fff; padding:20px; border-radius:8px; width:380px; text-align:center; box-shadow:0 4px 10px rgba(0,0,0,0.3);"> <h3 style="margin-top:0; color:#333;">🎙️ ஒலிப்பதிவு: <b>${currentWord}</b></h3> <p style="font-size:13px; color:#666;">ஒலிவாங்கியைக் கொண்டு சொல்லைத் தெளிவாகப் பேசவும்.</p> <div style="margin:15px 0;"> <button id="btn-start-rec" style="padding:8px 15px; background:#28a745; color:#fff; border:none; border-radius:4px; cursor:pointer;">▶️ பதிவு தொடங்கு</button> <button id="btn-stop-rec" style="padding:8px 15px; background:#dc3545; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">⏹️ நிறுத்து</button> </div> <audio id="rec-preview" controls style="display:none; width:100%; margin-top:10px;"></audio> <div id="rec-status" style="margin-top:10px; font-size:12px; font-weight:bold;"></div> <div style="margin-top:20px; display:flex; justify-content:space-between;"> <button id="btn-cancel-rec" style="padding:6px 12px; background:#6c757d; color:#fff; border:none; border-radius:4px; cursor:pointer;">ரத்து செய்</button> <button id="btn-upload-rec" style="padding:6px 12px; background:#007bff; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">☁️ பொதுவகத்தில் பதிவேற்று</button> </div> </div> </div>`; $('body').append(modalHtml); $('#btn-start-rec').on('click', async function () { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // Chrome/Edge-ல் webm மட்டுமே ஆதரவு. Firefox-ல் ogg ஆதரவு இருக்கலாம். const mimeType = MediaRecorder.isTypeSupported('audio/webm') ? 'audio/webm' : 'audio/ogg'; mediaRecorder = new MediaRecorder(stream, { mimeType: mimeType }); audioChunks = []; mediaRecorder.ondataavailable = event => audioChunks.push(event.data); mediaRecorder.onstop = async () => { const rawBlob = new Blob(audioChunks, { type: mediaRecorder.mimeType }); // webm என்றால் OGG ஆக மாற்ற வேண்டும் if (mediaRecorder.mimeType.includes('webm')) { $('#rec-status').text('OGG வடிவத்திற்கு மாற்றப்படுகிறது (சிறிது நேரம் ஆகலாம்)...').css('color', '#007bff'); $('#btn-upload-rec').prop('disabled', true).hide(); try { await loadFFmpeg(); const arrayBuffer = await rawBlob.arrayBuffer(); const uint8Array = new Uint8Array(arrayBuffer); ffmpegInstance.FS('writeFile', 'input.webm', uint8Array); // libvorbis குறியாக்கியைப் பயன்படுத்தி OGG ஆக மாற்றவும் await ffmpegInstance.run('-i', 'input.webm', '-acodec', 'libvorbis', '-q:a', '4', 'output.ogg'); const oggData = ffmpegInstance.FS('readFile', 'output.ogg'); recordedBlob = new Blob([oggData.buffer], { type: 'audio/ogg' }); // மாற்றப்பட்ட கோப்பை முன்னோட்டமாகக் காட்டவும் const audioUrl = URL.createObjectURL(recordedBlob); $('#rec-preview').attr('src', audioUrl).show(); $('#btn-upload-rec').prop('disabled', false).show(); $('#rec-status').text('OGG வடிவத்திற்கு மாற்றம் வெற்றி! கேட்டுப் பார்த்துப் பதிவேற்றவும்.').css('color', 'green'); } catch (err) { console.error("FFmpeg conversion error:", err); $('#rec-status').text('OGG ஆக மாற்றுவதில் பிழை. மீண்டும் முயற்சிக்கவும்.').css('color', 'red'); $('#btn-upload-rec').prop('disabled', false).show(); } } else { // ஏற்கனவே ogg என்றால் நேரடியாகப் பயன்படுத்தவும் recordedBlob = rawBlob; const audioUrl = URL.createObjectURL(recordedBlob); $('#rec-preview').attr('src', audioUrl).show(); $('#btn-upload-rec').show(); $('#rec-status').text('ஒலிப்பதிவு முடிந்தது. கேட்டுப் பார்த்துப் பதிவேற்றவும்.').css('color', 'green'); } }; mediaRecorder.start(); $(this).hide(); $('#btn-stop-rec').show(); $('#rec-status').text('பதிவாகிறது... பேசுங்கள்!').css('color', 'red'); } catch (err) { alert('ஒலிவாங்கி (Microphone) அனுமதியை வழங்கவும்.'); } }); $('#btn-stop-rec').on('click', function () { mediaRecorder.stop(); $(this).hide(); $('#btn-start-rec').show().text('🔄 மீண்டும் பதிவு செய்'); }); $('#btn-cancel-rec').on('click', function () { $('#wikt-recorder-modal').remove(); }); $('#btn-upload-rec').on('click', async function () { if (!recordedBlob) return; const $btn = $(this); $btn.prop('disabled', true).text('பதிவேற்றப்படுகிறது...'); $('#rec-status').text('விக்கிப் பொதுவகத்தில் பதிவேற்றப்படுகிறது...').css('color', '#007bff'); // கோப்பு பெயர் எப்போதும் .ogg ஆக இருக்க வேண்டும் const fileName = `Ta-${currentWord}.ogg`; const result = await uploadToCommons(fileName, recordedBlob); if (result.success) { $('#rec-status').text(`பதிவேற்றம் வெற்றி! (${result.fileName}) பக்கத்தில் இணைக்கப்படுகிறது...`).css('color', 'green'); await updateWiktionaryPage(currentWord, result.fileName, currentContent, wtApi); $('#wikt-recorder-modal').remove(); } else { $('#rec-status').text(`பதிவேற்றத்தில் பிழை: ${result.error}`).css('color', 'red'); $btn.prop('disabled', false).text('☁️ மீண்டும் முயற்சி செய்'); } }); } async function uploadToCommons(fileName, fileBlob) { try { const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); // 1. உள்நுழைவு நிலையைச் சரிபார்க்கவும் const userInfo = await commonsApi.get({ action: 'query', meta: 'userinfo', format: 'json' }); if (!userInfo.query || !userInfo.query.userinfo || userInfo.query.userinfo.anon !== undefined) { return { success: false, error: 'பொதுவகத்தில் உள்நுழையவில்லை. தயவுசெய்து முதலில் உள்நுழையவும்.' }; } // 2. CSRF டோக்கனைப் பெறவும் const tokenData = await commonsApi.get({ action: 'query', meta: 'tokens', format: 'json' }); const csrfToken = tokenData.query.tokens.csrftoken; // 3. கோப்புப் பெயர் ஏற்கனவே உள்ளதா எனச் சரிபார்த்து, இருந்தால் தானாக மறுபெயரிடவும் let finalFileName = fileName; const checkDup = await commonsApi.get({ action: 'query', titles: `File:${fileName}`, format: 'json' }); if (checkDup.query && checkDup.query.pages && checkDup.query.pages[-1] === undefined) { const timestamp = new Date().getTime(); finalFileName = fileName.replace('.ogg', `_${timestamp}.ogg`); console.log(`கோப்பு ஏற்கனவே உள்ளது, தானாக மறுபெயரிடப்பட்டது: ${finalFileName}`); } // 4. FormData ஐ உருவாக்கவும் const formData = new FormData(); formData.append('action', 'upload'); formData.append('filename', finalFileName); formData.append('token', csrfToken); formData.append('comment', `Tamil audio pronunciation for [[ta:${currentWord}]]`); formData.append('text', `== {{int:filedesc}} ==\n{{Information\n|description={{ta|1=தமிழ்: ${currentWord} என்பதன் ஒலிப்பு}}\n|date=${new Date().toISOString().split('T')[0]}\n|source={{own}}\n|author=[[User:Neyakkoo|Dr. Sathiyaraj Thangasamy]]\n}}\n\n== {{int:license-header}} ==\n{{CC-BY-SA-4.0}}\n\n[[Category:Tamil pronunciation]]`); formData.append('format', 'json'); formData.append('file', fileBlob, finalFileName); // 5. fetch ஐப் பயன்படுத்தி நேரடியாக அனுப்பவும், origin அளவுருவைச் சேர்க்கவும் const url = `https://commons.wikimedia.org/w/api.php?origin=${encodeURIComponent(location.origin)}`; const response = await fetch(url, { method: 'POST', body: formData, credentials: 'include' }); const res = await response.json(); if (res && res.upload && res.upload.result === 'Success') { return { success: true, fileName: finalFileName }; } else { console.error('Commons Upload Raw Response:', res); let errorMsg = 'தெரியாத பிழை'; if (res && res.error) { errorMsg = `${res.error.code}: ${res.error.info}`; } else if (res && res.upload && res.upload.warnings) { errorMsg = JSON.stringify(res.upload.warnings); } return { success: false, error: errorMsg }; } } catch (e) { console.error('Commons Upload Exception:', e); return { success: false, error: e.message || 'பதிவேற்றத்தில் விதிவிலக்கு ஏற்பட்டது' }; } } async function updateWiktionaryPage(word, fileName, currentContent, wtApi, $btnUI) { // ஒலிப்பு வார்ப்புருவை உருவாக்கவும் const targetAudioTemplate = `{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}`; let newContent = currentContent; // பழைய ஒலிப்புப் பதிவுகளை நீக்கவும் newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); if (newContent.includes('=={{மொழி|ta}}==')) { newContent = newContent.replace('=={{மொழி|ta}}==', `=={{மொழி|ta}}==\n\n${targetAudioTemplate}`); } else if (newContent.includes('== {{மொழி|ta}} ==')) { newContent = newContent.replace('== {{மொழி|ta}} ==', `== {{மொழி|ta}} ==\n\n${targetAudioTemplate}`); } else if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* ${targetAudioTemplate}`); } else { newContent = `=={{மொழி|ta}}==\n\n${targetAudioTemplate}\n\n` + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: word, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}` }); if ($btnUI) $btnUI.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); } catch (err) { if ($btnUI) $btnUI.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } } $(document).ready(checkAndLinkAudio); })(); 4khphslm76bu9gliuahyr8jhstywufr 2004692 2004688 2026-10-26T23:07:15Z Neyakkoo 16078 பயிற்சி 2004692 javascript text/javascript // ============================================================ // Wiktionary Audio Recorder & Linker (WebM to OGG via ffmpeg.wasm) // Author: Dr. Sathiyaraj Thangasamy (Neyakkoo) // ============================================================ (function () { 'use strict'; if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); const SCRIPT_RE = /[\u0B80-\u0BFF]/; if (!SCRIPT_RE.test(currentWord)) return; let mediaRecorder, audioChunks = [], recordedBlob = null; let ffmpegInstance = null; // FFmpeg ஐ தேவைப்படும்போது மட்டும் ஏற்றவும் async function loadFFmpeg() { if (ffmpegInstance) return ffmpegInstance; if (typeof FFmpeg === 'undefined') { throw new Error('FFmpeg நூலகம் ஏற்றப்படவில்லை. ஸ்கிரிப்ட் இணைப்பைச் சரிபார்க்கவும்.'); } const { createFFmpeg } = FFmpeg; ffmpegInstance = createFFmpeg({ log: false }); await ffmpegInstance.load(); return ffmpegInstance; } async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); if (!pageData.query || !pageData.query.pages || pageData.query.pages[0].missing) return; const content = pageData.query.pages[0].revisions[0].content; if (content.includes('{{ஒலிக்கோப்பு-தமிழ்') || content.includes('{{audio|') || content.includes('{{ஒலிப்பு|')) { console.log('இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்புக் கோப்பு உள்ளது.'); return; } const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); const searchQuery = `filetype:audio "${currentWord}"`; const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: searchQuery, srnamespace: 6, srlimit: 5, format: 'json' }); const hits = (res && res.query && res.query.search) || []; let matchedFile = null; for (const item of hits) { const title = item.title.replace(/^File:/i, ''); if (title.toLowerCase().includes(currentWord.toLowerCase())) { matchedFile = title; break; } } if (matchedFile) { showAudioNoticeButton(matchedFile, content, wtApi); } else { showRecordAudioButton(content, wtApi); } } catch (e) { console.warn('ஒலிப்புக் கோப்புத் தேடலில் பிழை:', e); } } function showAudioNoticeButton(fileName, currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `பொதுவகக் கோப்பு: ${fileName}` }) .html(`🔊 <b>ஒலிப்புக் கோப்பு இணைக்கவா?</b> (${fileName})`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#00a38f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', async function () { $(this).prop('disabled', true).text('இணைக்கப்படுகிறது...'); await updateWiktionaryPage(currentWord, fileName, currentContent, wtApi, $(this)); }); $('#firstHeading').append(btn); } function showRecordAudioButton(currentContent, wtApi) { const btn = $('<button>', { id: 'wikt-record-audio-btn', type: 'button', title: 'சொல்லிற்கான புதிய ஒலிப்பை பதிவு செய்' }) .html(`🎙️ <b>ஒலிப்பதிவு செய்</b>`) .css({ 'margin-left': '12px', 'padding': '4px 10px', 'background-color': '#d9534f', 'color': '#ffffff', 'border': 'none', 'border-radius': '5px', 'cursor': 'pointer', 'font-size': '12px' }) .on('click', function () { openRecorderModal(currentContent, wtApi); }); $('#firstHeading').append(btn); } function openRecorderModal(currentContent, wtApi) { $('#wikt-recorder-modal').remove(); const modalHtml = ` <div id="wikt-recorder-modal" style="position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.6); z-index:10000; display:flex; align-items:center; justify-content:center;"> <div style="background:#fff; padding:20px; border-radius:8px; width:380px; text-align:center; box-shadow:0 4px 10px rgba(0,0,0,0.3);"> <h3 style="margin-top:0; color:#333;">🎙️ ஒலிப்பதிவு: <b>${currentWord}</b></h3> <p style="font-size:13px; color:#666;">ஒலிவாங்கியைக் கொண்டு சொல்லைத் தெளிவாகப் பேசவும்.</p> <div style="margin:15px 0;"> <button id="btn-start-rec" style="padding:8px 15px; background:#28a745; color:#fff; border:none; border-radius:4px; cursor:pointer;">▶️ பதிவு தொடங்கு</button> <button id="btn-stop-rec" style="padding:8px 15px; background:#dc3545; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">⏹️ நிறுத்து</button> </div> <audio id="rec-preview" controls style="display:none; width:100%; margin-top:10px;"></audio> <div id="rec-status" style="margin-top:10px; font-size:12px; font-weight:bold;"></div> <div style="margin-top:20px; display:flex; justify-content:space-between;"> <button id="btn-cancel-rec" style="padding:6px 12px; background:#6c757d; color:#fff; border:none; border-radius:4px; cursor:pointer;">ரத்து செய்</button> <button id="btn-upload-rec" style="padding:6px 12px; background:#007bff; color:#fff; border:none; border-radius:4px; cursor:pointer; display:none;">☁️ பொதுவகத்தில் பதிவேற்று</button> </div> </div> </div>`; $('body').append(modalHtml); $('#btn-start-rec').on('click', async function () { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const mimeType = MediaRecorder.isTypeSupported('audio/webm') ? 'audio/webm' : 'audio/ogg'; mediaRecorder = new MediaRecorder(stream, { mimeType: mimeType }); audioChunks = []; mediaRecorder.ondataavailable = event => audioChunks.push(event.data); mediaRecorder.onstop = async () => { const rawBlob = new Blob(audioChunks, { type: mediaRecorder.mimeType }); // webm என்றால் OGG ஆக மாற்ற வேண்டும் if (mediaRecorder.mimeType.includes('webm')) { $('#rec-status').text('OGG வடிவத்திற்கு மாற்றப்படுகிறது (சிறிது நேரம் ஆகலாம்)...').css('color', '#007bff'); $('#btn-upload-rec').prop('disabled', true).hide(); try { const ffmpeg = await loadFFmpeg(); const arrayBuffer = await rawBlob.arrayBuffer(); const uint8Array = new Uint8Array(arrayBuffer); ffmpeg.FS('writeFile', 'input.webm', uint8Array); // libvorbis குறியாக்கியைப் பயன்படுத்தி OGG ஆக மாற்றவும் await ffmpeg.run('-i', 'input.webm', '-acodec', 'libvorbis', '-q:a', '4', 'output.ogg'); const oggData = ffmpeg.FS('readFile', 'output.ogg'); recordedBlob = new Blob([oggData.buffer], { type: 'audio/ogg' }); const audioUrl = URL.createObjectURL(recordedBlob); $('#rec-preview').attr('src', audioUrl).show(); $('#btn-upload-rec').prop('disabled', false).show(); $('#rec-status').text('OGG வடிவத்திற்கு மாற்றம் வெற்றி! கேட்டுப் பார்த்துப் பதிவேற்றவும்.').css('color', 'green'); } catch (err) { console.error("FFmpeg conversion error:", err); $('#rec-status').text(`OGG ஆக மாற்றுவதில் பிழை: ${err.message || 'மீண்டும் முயற்சிக்கவும்.'}`).css('color', 'red'); $('#btn-upload-rec').prop('disabled', false).show(); } } else { // ஏற்கனவே ogg என்றால் நேரடியாகப் பயன்படுத்தவும் recordedBlob = rawBlob; const audioUrl = URL.createObjectURL(recordedBlob); $('#rec-preview').attr('src', audioUrl).show(); $('#btn-upload-rec').show(); $('#rec-status').text('ஒலிப்பதிவு முடிந்தது. கேட்டுப் பார்த்துப் பதிவேற்றவும்.').css('color', 'green'); } }; mediaRecorder.start(); $(this).hide(); $('#btn-stop-rec').show(); $('#rec-status').text('பதிவாகிறது... பேசுங்கள்!').css('color', 'red'); } catch (err) { alert('ஒலிவாங்கி (Microphone) அனுமதியை வழங்கவும்.'); } }); $('#btn-stop-rec').on('click', function () { mediaRecorder.stop(); $(this).hide(); $('#btn-start-rec').show().text('🔄 மீண்டும் பதிவு செய்'); }); $('#btn-cancel-rec').on('click', function () { $('#wikt-recorder-modal').remove(); }); $('#btn-upload-rec').on('click', async function () { if (!recordedBlob) return; const $btn = $(this); $btn.prop('disabled', true).text('பதிவேற்றப்படுகிறது...'); $('#rec-status').text('விக்கிப் பொதுவகத்தில் பதிவேற்றப்படுகிறது...').css('color', '#007bff'); // கோப்பு பெயர் எப்போதும் .ogg ஆக இருக்க வேண்டும் const fileName = `Ta-${currentWord}.ogg`; const result = await uploadToCommons(fileName, recordedBlob); if (result.success) { $('#rec-status').text(`பதிவேற்றம் வெற்றி! (${result.fileName}) பக்கத்தில் இணைக்கப்படுகிறது...`).css('color', 'green'); await updateWiktionaryPage(currentWord, result.fileName, currentContent, wtApi); $('#wikt-recorder-modal').remove(); } else { $('#rec-status').text(`பதிவேற்றத்தில் பிழை: ${result.error}`).css('color', 'red'); $btn.prop('disabled', false).text('☁️ மீண்டும் முயற்சி செய்'); } }); } async function uploadToCommons(fileName, fileBlob) { try { const commonsApi = new mw.ForeignApi('https://commons.wikimedia.org/w/api.php'); // 1. உள்நுழைவு நிலையைச் சரிபார்க்கவும் const userInfo = await commonsApi.get({ action: 'query', meta: 'userinfo', format: 'json' }); if (!userInfo.query || !userInfo.query.userinfo || userInfo.query.userinfo.anon !== undefined) { return { success: false, error: 'பொதுவகத்தில் உள்நுழையவில்லை. தயவுசெய்து முதலில் உள்நுழையவும்.' }; } // 2. CSRF டோக்கனைப் பெறவும் const tokenData = await commonsApi.get({ action: 'query', meta: 'tokens', format: 'json' }); const csrfToken = tokenData.query.tokens.csrftoken; // 3. கோப்புப் பெயர் ஏற்கனவே உள்ளதா எனச் சரிபார்த்து, இருந்தால் தானாக மறுபெயரிடவும் let finalFileName = fileName; const checkDup = await commonsApi.get({ action: 'query', titles: `File:${fileName}`, format: 'json' }); if (checkDup.query && checkDup.query.pages && checkDup.query.pages[-1] === undefined) { const timestamp = new Date().getTime(); finalFileName = fileName.replace('.ogg', `_${timestamp}.ogg`); console.log(`கோப்பு ஏற்கனவே உள்ளது, தானாக மறுபெயரிடப்பட்டது: ${finalFileName}`); } // 4. FormData ஐ உருவாக்கவும் const formData = new FormData(); formData.append('action', 'upload'); formData.append('filename', finalFileName); formData.append('token', csrfToken); formData.append('comment', `Tamil audio pronunciation for [[ta:${currentWord}]]`); formData.append('text', `== {{int:filedesc}} ==\n{{Information\n|description={{ta|1=தமிழ்: ${currentWord} என்பதன் ஒலிப்பு}}\n|date=${new Date().toISOString().split('T')[0]}\n|source={{own}}\n|author=[[User:Neyakkoo|Dr. Sathiyaraj Thangasamy]]\n}}\n\n== {{int:license-header}} ==\n{{CC-BY-SA-4.0}}\n\n[[Category:Tamil pronunciation]]`); formData.append('format', 'json'); formData.append('file', fileBlob, finalFileName); // 5. fetch ஐப் பயன்படுத்தி நேரடியாக அனுப்பவும், origin அளவுருவைச் சேர்க்கவும் const url = `https://commons.wikimedia.org/w/api.php?origin=${encodeURIComponent(location.origin)}`; const response = await fetch(url, { method: 'POST', body: formData, credentials: 'include' }); const res = await response.json(); if (res && res.upload && res.upload.result === 'Success') { return { success: true, fileName: finalFileName }; } else { console.error('Commons Upload Raw Response:', res); let errorMsg = 'தெரியாத பிழை'; if (res && res.error) { errorMsg = `${res.error.code}: ${res.error.info}`; } else if (res && res.upload && res.upload.warnings) { errorMsg = JSON.stringify(res.upload.warnings); } return { success: false, error: errorMsg }; } } catch (e) { console.error('Commons Upload Exception:', e); return { success: false, error: e.message || 'பதிவேற்றத்தில் விதிவிலக்கு ஏற்பட்டது' }; } } async function updateWiktionaryPage(word, fileName, currentContent, wtApi, $btnUI) { const targetAudioTemplate = `{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}}`; let newContent = currentContent; newContent = newContent.replace(/\*\s*Ta-[^\n]+\(கோப்பு\)/gi, ''); if (newContent.includes('=={{மொழி|ta}}==')) { newContent = newContent.replace('=={{மொழி|ta}}==', `=={{மொழி|ta}}==\n\n${targetAudioTemplate}`); } else if (newContent.includes('== {{மொழி|ta}} ==')) { newContent = newContent.replace('== {{மொழி|ta}} ==', `== {{மொழி|ta}} ==\n\n${targetAudioTemplate}`); } else if (newContent.includes('=== ஒலிப்பு ===')) { newContent = newContent.replace('=== ஒலிப்பு ===', `=== ஒலிப்பு ===\n* ${targetAudioTemplate}`); } else { newContent = `=={{மொழி|ta}}==\n\n${targetAudioTemplate}\n\n` + newContent; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: word, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: {{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}` }); if ($btnUI) $btnUI.css('background-color', '#2e7d32').text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); setTimeout(() => location.reload(), 1000); } catch (err) { if ($btnUI) $btnUI.css('background-color', '#c62828').text('❌ பிழை ஏற்பட்டது'); console.error(err); } } $(document).ready(checkAndLinkAudio); })(); qvbsmumcax41va3w4m722c5aytj5nwf 2004694 2004692 2026-10-26T23:15:52Z Neyakkoo 16078 பயிற்சி 2004694 javascript text/javascript /* ============================================================ * Wiktionary Audio Recorder & Linker (WebM -> OGG) * Tamil Wiktionary + Wikimedia Commons * ============================================================ */ (function () { 'use strict'; if (!window.mw || !window.$) return; if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); if (!/[\u0B80-\u0BFF]/.test(currentWord)) return; const FFMPEG_SCRIPT = 'https://unpkg.com/@ffmpeg/ffmpeg@0.11.6/dist/ffmpeg.min.js'; const FFMPEG_CORE = 'https://unpkg.com/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js'; const COMMONS_API_URL = 'https://commons.wikimedia.org/w/api.php'; const AUDIO_TEMPLATE = '{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}'; let mediaRecorder = null; let audioChunks = []; let recordedBlob = null; let ffmpegInstance = null; let activeStream = null; let previewUrl = null; function setStatus(message, color) { $('#rec-status').text(message).css('color', color || '#333'); } function explainError(error) { if (!error) return 'தெரியாத பிழை'; if (typeof error === 'string') return error; if (error.info) return `${error.code || 'API error'}: ${error.info}`; if (error.message) return error.message; try { return JSON.stringify(error); } catch (_) { return String(error); } } /* Load FFmpeg for converting WebM audio to OGG. */ async function loadFFmpeg() { if (ffmpegInstance) return ffmpegInstance; if ( !window.FFmpeg || typeof window.FFmpeg.createFFmpeg !== 'function' ) { await mw.loader.getScript(FFMPEG_SCRIPT); } if ( !window.FFmpeg || typeof window.FFmpeg.createFFmpeg !== 'function' ) { throw new Error( 'FFmpeg நூலகம் ஏற்றப்படவில்லை. இணைய இணைப்பு அல்லது Content Security Policy-ஐச் சரிபார்க்கவும்.' ); } ffmpegInstance = window.FFmpeg.createFFmpeg({ log: false, corePath: FFMPEG_CORE }); await ffmpegInstance.load(); return ffmpegInstance; } /* Convert recorded audio to actual OGG/Vorbis format. */ async function convertToOgg(webmBlob) { const ffmpeg = await loadFFmpeg(); const inputName = 'recording-input.webm'; const outputName = 'recording-output.ogg'; const bytes = new Uint8Array( await webmBlob.arrayBuffer() ); try { ffmpeg.FS('unlink', inputName); } catch (_) {} try { ffmpeg.FS('unlink', outputName); } catch (_) {} ffmpeg.FS('writeFile', inputName, bytes); await ffmpeg.run( '-i', inputName, '-vn', '-ac', '1', '-ar', '44100', '-c:a', 'libvorbis', '-q:a', '4', outputName ); const output = ffmpeg.FS('readFile', outputName); if (!output || !output.length) { throw new Error( 'FFmpeg வெற்று OGG கோப்பை உருவாக்கியது.' ); } return new Blob([output], { type: 'audio/ogg' }); } /* Check the Wiktionary page and Commons for existing audio. */ async function checkAndLinkAudio() { const wtApi = new mw.Api(); try { const pageData = await wtApi.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); const page = pageData?.query?.pages?.[0]; if (!page || page.missing || !page.revisions?.[0]) { return; } const content = page.revisions[0].content || ''; if ( /\{\{\s*(ஒலிக்கோப்பு-தமிழ்|audio|ஒலிப்பு)\b/i.test(content) ) { console.log( 'இந்தச் சொல் பக்கத்தில் ஏற்கனவே ஒலிப்பு வார்ப்புரு உள்ளது.' ); return; } const commonsApi = new mw.ForeignApi(COMMONS_API_URL); const res = await commonsApi.get({ action: 'query', list: 'search', srsearch: `filetype:audio "${currentWord}"`, srnamespace: 6, srlimit: 10, format: 'json' }); const hits = res?.query?.search || []; const matched = hits.find(item => { const title = (item.title || '').replace( /^File:/i, '' ); return ( title.toLocaleLowerCase().includes( currentWord.toLocaleLowerCase() ) && /\.ogg$/i.test(title) ); }); if (matched) { showAudioNoticeButton( matched.title.replace(/^File:/i, ''), content, wtApi ); } else { showRecordAudioButton(content, wtApi); } } catch (error) { console.warn( 'ஒலிப்புக் கோப்பைத் தேடுவதில் பிழை:', error ); showRecordAudioButton('', wtApi); } } function styleButton($button, background) { $button.css({ marginLeft: '12px', padding: '5px 10px', backgroundColor: background, color: '#fff', border: '0', borderRadius: '5px', cursor: 'pointer', fontSize: '12px' }); } function showAudioNoticeButton( fileName, currentContent, wtApi ) { if ($('#wikt-single-audio-btn').length) return; const $btn = $('<button>', { id: 'wikt-single-audio-btn', type: 'button', title: `Commons கோப்பு: ${fileName}` }).text( `🔊 ஒலிப்பை விக்சனரியில் இணைக்கவும் (${fileName})` ); styleButton($btn, '#008577'); $btn.on('click', async function () { $btn.prop('disabled', true) .text('இணைக்கப்படுகிறது...'); try { await updateWiktionaryPage( currentWord, fileName, currentContent, wtApi, $btn ); } catch (error) { console.error(error); $btn.prop('disabled', false) .text('மீண்டும் முயற்சி செய்'); } }); $('#firstHeading').append($btn); } function showRecordAudioButton(currentContent, wtApi) { if ($('#wikt-record-audio-btn').length) return; const $btn = $('<button>', { id: 'wikt-record-audio-btn', type: 'button', title: 'சொல்லிற்கான புதிய ஒலிப்பைப் பதிவு செய்யவும்' }).text('🎙️ ஒலிப்பதிவு செய்'); styleButton($btn, '#c43d35'); $btn.on('click', function () { openRecorderModal(currentContent, wtApi); }); $('#firstHeading').append($btn); } function closeRecorderModal() { try { if ( mediaRecorder && mediaRecorder.state !== 'inactive' ) { mediaRecorder.stop(); } } catch (_) {} if (activeStream) { activeStream.getTracks().forEach( track => track.stop() ); activeStream = null; } if (previewUrl) { URL.revokeObjectURL(previewUrl); previewUrl = null; } $('#wikt-recorder-modal').remove(); } /* Recorder modal UI. */ function openRecorderModal(currentContent, wtApi) { $('#wikt-recorder-modal').remove(); recordedBlob = null; audioChunks = []; const modalHtml = ` <div id="wikt-recorder-modal" style="position:fixed;inset:0;background:rgba(0,0,0,.65); z-index:10000;display:flex;align-items:center; justify-content:center;padding:16px;"> <div style="background:#fff;color:#222;padding:20px; border-radius:8px;width:min(440px,95vw); text-align:center;box-shadow:0 4px 12px rgba(0,0,0,.3);"> <h3 style="margin-top:0;">🎙️ ஒலிப்பதிவு: <b></b></h3> <p style="font-size:13px;color:#555;"> ஒலிவாங்கியில் சொல்லைத் தெளிவாகப் பேசவும். பதிவேற்றத்திற்கு முன் OGG வடிவமாக மாற்றப்படும். </p> <div style="margin:15px 0;"> <button id="btn-start-rec" type="button"> ▶️ பதிவு தொடங்கு </button> <button id="btn-stop-rec" type="button" style="display:none;">⏹️ நிறுத்து</button> </div> <audio id="rec-preview" controls style="display:none;width:100%;margin-top:10px;"></audio> <div id="rec-status" role="status" style="margin-top:10px;font-size:12px;font-weight:bold;"></div> <div style="margin-top:20px;display:flex; justify-content:space-between;gap:10px;"> <button id="btn-cancel-rec" type="button"> ரத்து செய் </button> <button id="btn-upload-rec" type="button" style="display:none;"> ☁️ Commons-ல் பதிவேற்று </button> </div> </div> </div>`; $('body').append(modalHtml); $('#wikt-recorder-modal h3 b').text(currentWord); $('#wikt-recorder-modal button').css({ padding: '7px 11px', border: 0, borderRadius: '4px', cursor: 'pointer' }); $('#btn-start-rec').css({ background: '#218838', color: '#fff' }); $('#btn-stop-rec').css({ background: '#c82333', color: '#fff' }); $('#btn-cancel-rec').css({ background: '#6c757d', color: '#fff' }); $('#btn-upload-rec').css({ background: '#007bff', color: '#fff' }); $('#btn-start-rec').on('click', async function () { try { if ( !navigator.mediaDevices?.getUserMedia || !window.MediaRecorder ) { throw new Error( 'இந்த உலாவியில் ஒலிப்பதிவு வசதி ஆதரிக்கப்படவில்லை.' ); } activeStream = await navigator.mediaDevices.getUserMedia({ audio: true }); const mimeCandidates = [ 'audio/webm;codecs=opus', 'audio/webm', 'audio/ogg;codecs=opus', 'audio/ogg' ]; const mimeType = mimeCandidates.find( type => MediaRecorder.isTypeSupported(type) ); if (!mimeType) { throw new Error( 'WebM/OGG ஒலிப்பதிவு வடிவம் இந்த உலாவியில் ஆதரிக்கப்படவில்லை.' ); } mediaRecorder = new MediaRecorder( activeStream, { mimeType } ); audioChunks = []; mediaRecorder.ondataavailable = event => { if (event.data && event.data.size) { audioChunks.push(event.data); } }; mediaRecorder.onerror = event => { setStatus( `ஒலிப்பதிவு பிழை: ${explainError(event.error)}`, 'red' ); }; mediaRecorder.onstop = async () => { try { const rawBlob = new Blob(audioChunks, { type: mediaRecorder.mimeType }); if (!rawBlob.size) { throw new Error( 'ஒலிப்பதிவு காலியாக உள்ளது. மீண்டும் பதிவு செய்யவும்.' ); } if ( rawBlob.type.toLowerCase().includes('ogg') ) { recordedBlob = new Blob([rawBlob], { type: 'audio/ogg' }); } else { setStatus( 'WebM ஒலியை OGG ஆக மாற்றுகிறது. காத்திருக்கவும்...', '#007bff' ); $('#btn-upload-rec') .prop('disabled', true) .hide(); recordedBlob = await convertToOgg(rawBlob); } if ( !recordedBlob || !recordedBlob.size || recordedBlob.type !== 'audio/ogg' ) { throw new Error( 'சரியான OGG கோப்பை உருவாக்க முடியவில்லை.' ); } if (previewUrl) { URL.revokeObjectURL(previewUrl); } previewUrl = URL.createObjectURL(recordedBlob); $('#rec-preview') .attr('src', previewUrl) .show(); $('#btn-upload-rec') .prop('disabled', false) .show(); setStatus( `OGG மாற்றம் வெற்றி (${Math.ceil( recordedBlob.size / 1024 )} KB). கேட்டுப் பார்த்துப் பதிவேற்றவும்.`, 'green' ); } catch (error) { console.error( 'OGG conversion error:', error ); recordedBlob = null; setStatus( `OGG மாற்றப் பிழை: ${explainError(error)}`, 'red' ); $('#btn-upload-rec') .prop('disabled', true) .hide(); } finally { if (activeStream) { activeStream.getTracks().forEach( track => track.stop() ); activeStream = null; } } }; mediaRecorder.start(); $(this).hide(); $('#btn-stop-rec').show(); $('#btn-upload-rec').hide(); setStatus( 'பதிவு நடைபெறுகிறது... பேசுங்கள்.', 'red' ); } catch (error) { console.error(error); if (activeStream) { activeStream.getTracks().forEach( track => track.stop() ); activeStream = null; } setStatus( `பதிவைத் தொடங்க முடியவில்லை: ${explainError(error)}`, 'red' ); } }); $('#btn-stop-rec').on('click', function () { if ( mediaRecorder && mediaRecorder.state !== 'inactive' ) { mediaRecorder.stop(); } $(this).hide(); $('#btn-start-rec') .show() .text('🔄 மீண்டும் பதிவு செய்'); }); $('#btn-cancel-rec').on( 'click', closeRecorderModal ); $('#btn-upload-rec').on( 'click', async function () { if ( !recordedBlob || recordedBlob.type !== 'audio/ogg' ) { setStatus( 'பதிவேற்றத்துக்கு முன் செல்லுபடியான OGG கோப்பு தேவை.', 'red' ); return; } const $btn = $(this) .prop('disabled', true) .text('பதிவேற்றப்படுகிறது...'); setStatus( 'Wikimedia Commons-ல் OGG கோப்பு பதிவேற்றப்படுகிறது...', '#007bff' ); try { const fileName = `Ta-${currentWord}.ogg`; const result = await uploadToCommons( fileName, recordedBlob ); setStatus( 'பதிவேற்றம் வெற்றி. விக்சனரி பக்கத்தில் இணைக்கப்படுகிறது...', 'green' ); await updateWiktionaryPage( currentWord, result.fileName, currentContent, wtApi ); closeRecorderModal(); } catch (error) { console.error('Upload/link error:', error); setStatus( `பிழை: ${explainError(error)}`, 'red' ); $btn.prop('disabled', false) .text('☁️ மீண்டும் முயற்சி செய்'); } } ); } /* Upload an OGG file to Wikimedia Commons. */ async function uploadToCommons(fileName, fileBlob) { if ( !(fileBlob instanceof Blob) || fileBlob.type !== 'audio/ogg' || !fileBlob.size ) { throw new Error( 'பதிவேற்றக் கோப்பு செல்லுபடியான audio/ogg கோப்பு அல்ல.' ); } const commonsApi = new mw.ForeignApi(COMMONS_API_URL); const userInfo = await commonsApi.get({ action: 'query', meta: 'userinfo', format: 'json', assert: 'user' }); if ( !userInfo?.query?.userinfo || userInfo.query.userinfo.anon !== undefined ) { throw new Error( 'Commons-ல் உள்நுழையவில்லை. commons.wikimedia.org-ல் உள்நுழைந்து மீண்டும் முயற்சிக்கவும்.' ); } /* Do not overwrite an existing file. */ const titleCheck = await commonsApi.get({ action: 'query', titles: `File:${fileName}`, formatversion: 2, format: 'json' }); const existing = titleCheck?.query?.pages?.[0]; if (existing && !existing.missing) { throw new Error( `Commons-ல் File:${fileName} ஏற்கனவே உள்ளது. ஏற்கனவே உள்ள கோப்பை இணைக்கவும்; புதிய பதிவேற்றம் நிறுத்தப்பட்டது.` ); } const description = [ '== {{int:filedesc}} ==', '{{Information', `|description={{ta|தமிழ்: ${currentWord} என்பதன் ஒலிப்பு}}`, `|date=${new Date().toISOString().slice(0, 10)}`, '|source={{own}}', '|author=[[User:Neyakkoo|Dr. Sathiyaraj Thangasamy]]', '}}', '', '== {{int:license-header}} ==', '{{CC-BY-SA-4.0}}', '', '[[Category:Tamil pronunciation]]', '[[Category:Files uploaded by Spell4Wiki in ta]]' ].join('\n'); const response = await commonsApi.upload( fileBlob, { filename: fileName, comment: `Tamil pronunciation audio for [[ta:${currentWord}]]`, text: description, format: 'json', assert: 'user' } ); if (response?.upload?.result !== 'Success') { if (response?.error) { throw new Error( `${response.error.code}: ${response.error.info}` ); } throw new Error( `Commons பதிவேற்றம் வெற்றிபெறவில்லை: ${JSON.stringify(response)}` ); } return { success: true, fileName: response.upload.filename || fileName }; } /* Add the audio template to the Tamil Wiktionary page. */ async function updateWiktionaryPage( word, fileName, currentContent, wtApi, $btnUI ) { let newContent = currentContent || ''; if ( /\{\{\s*(ஒலிக்கோப்பு-தமிழ்|audio|ஒலிப்பு)\b/i.test(newContent) ) { if ($btnUI) { $btnUI.text( 'ஏற்கனவே ஒலிப்பு வார்ப்புரு உள்ளது' ).prop('disabled', false); } return; } const insertion = `${AUDIO_TEMPLATE}\n`; const tamilHeading = /(^==\s*\{\{மொழி\|ta\}\}\s*==\s*)/m; if (tamilHeading.test(newContent)) { newContent = newContent.replace( tamilHeading, `$1\n${insertion}` ); } else if ( /^===\s*ஒலிப்பு\s*===/m.test(newContent) ) { newContent = newContent.replace( /(^===\s*ஒலிப்பு\s*===\s*)/m, `$1\n* ${AUDIO_TEMPLATE}\n` ); } else { newContent = `=={{மொழி|ta}}==\n\n${insertion}\n${newContent}`; } try { await wtApi.postWithToken('csrf', { action: 'edit', title: word, text: newContent, summary: `விக்கிப் பொதுவகத்திலிருந்து ஒலிப்புக் கோப்பு இணைக்கப்பட்டது: ${fileName}`, format: 'json' }); if ($btnUI) { $btnUI.css( 'background-color', '#2e7d32' ).text('✅ ஒலிப்பு இணைக்கப்பட்டது!'); } setTimeout(() => location.reload(), 1200); } catch (error) { console.error( 'Wiktionary edit error:', error ); if ($btnUI) { $btnUI.css( 'background-color', '#c62828' ).text('❌ பக்கத் திருத்தம் தோல்வி'); } throw error; } } $(checkAndLinkAudio); })(); pcs7nc7nc59z1el03lzbgcqwijngj7w 2004695 2004694 2026-10-26T23:18:23Z Neyakkoo 16078 பயிற்சி 2004695 javascript text/javascript /* ============================================================ * Tamil Wiktionary Audio Recorder & Linker * Records OGG/Opus and uploads to Wikimedia Commons * ============================================================ */ (function () { 'use strict'; if (!window.mw || !window.$) return; if (mw.config.get('wgNamespaceNumber') !== 0) return; const currentWord = mw.config.get('wgTitle'); if (!/[\u0B80-\u0BFF]/.test(currentWord)) return; const COMMONS_API_URL = 'https://commons.wikimedia.org/w/api.php'; const RECORDER_CDN = 'https://cdn.jsdelivr.net/npm/opus-media-recorder@0.8.0'; const AUDIO_TEMPLATE = '{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}'; let recorder = null; let audioChunks = []; let recordedBlob = null; let activeStream = null; let previewUrl = null; let recorderLibraryPromise = null; function setStatus(message, color = '#333') { $('#rec-status').text(message).css('color', color); } function errorMessage(error) { if (!error) return 'தெரியாத பிழை'; if (error.info) return error.info; if (error.message) return error.message; return String(error); } function loadScript(url) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = url; script.onload = resolve; script.onerror = () => reject( new Error('JavaScript நூலகத்தை ஏற்ற முடியவில்லை: ' + url) ); document.head.appendChild(script); }); } /* Load OGG recorder library once. */ async function loadOggRecorder() { if (window.OpusMediaRecorder) { return window.OpusMediaRecorder; } if (recorderLibraryPromise) { return recorderLibraryPromise; } recorderLibraryPromise = (async () => { await loadScript( RECORDER_CDN + '/OpusMediaRecorder.umd.js' ); if (!window.OpusMediaRecorder) { throw new Error( 'OpusMediaRecorder நூலகம் கிடைக்கவில்லை.' ); } return window.OpusMediaRecorder; })(); try { return await recorderLibraryPromise; } catch (error) { recorderLibraryPromise = null; throw error; } } function stopMicrophone() { if (activeStream) { activeStream.getTracks().forEach(track => track.stop()); activeStream = null; } } function releasePreview() { if (previewUrl) { URL.revokeObjectURL(previewUrl); previewUrl = null; } } function closeModal() { try { if (recorder && recorder.state !== 'inactive') { recorder.stop(); } } catch (error) { console.warn(error); } stopMicrophone(); releasePreview(); recorder = null; recordedBlob = null; $('#wikt-recorder-modal').remove(); } function styleButton($button, background, color = '#fff') { $button.css({ padding: '8px 12px', border: '0', borderRadius: '5px', background: background, color: color, cursor: 'pointer', margin: '4px' }); } /* Add recorder button to the page heading. */ function showRecordButton() { if ($('#wikt-record-audio-btn').length) return; const $button = $('<button>', { id: 'wikt-record-audio-btn', type: 'button' }).text('🎙️ ஒலிப்பதிவு செய்'); styleButton($button, '#b83232'); $button.on('click', openRecorderModal); $('#firstHeading').append($button); } /* Load recorder worker and encoder files from the same CDN version. */ function getWorkerOptions() { return { encoderWorkerFactory: function () { return new Worker( RECORDER_CDN + '/encoderWorker.umd.js' ); }, OggOpusEncoderWasmPath: RECORDER_CDN + '/OggOpusEncoder.wasm', WebMOpusEncoderWasmPath: RECORDER_CDN + '/WebMOpusEncoder.wasm' }; } function openRecorderModal() { $('#wikt-recorder-modal').remove(); audioChunks = []; recordedBlob = null; const html = ` <div id="wikt-recorder-modal" style="position:fixed;inset:0;background:rgba(0,0,0,.65); z-index:99999;display:flex;align-items:center; justify-content:center;padding:15px;"> <div style="background:white;color:#222;padding:22px; border-radius:9px;width:min(470px,95vw); box-shadow:0 4px 20px rgba(0,0,0,.3);"> <h2 style="text-align:center;margin-top:0;"> 🎙️ ஒலிப்பதிவு: <span id="rec-word"></span> </h2> <p style="text-align:center;font-size:14px;"> சொல்லைத் தெளிவாகப் பதிவு செய்யவும். பதிவேற்றப்படும் கோப்பு OGG வடிவில் இருக்க வேண்டும். </p> <div style="text-align:center;margin:18px 0;"> <button id="btn-start-rec" type="button"> ▶️ பதிவு தொடங்கு </button> <button id="btn-stop-rec" type="button" hidden> ⏹️ நிறுத்து </button> </div> <audio id="rec-preview" controls style="display:none;width:100%;margin:12px 0;"> </audio> <div id="rec-status" role="status" style="font-size:13px;text-align:center; font-weight:bold;overflow-wrap:anywhere;"> </div> <div style="display:flex;justify-content:space-between; flex-wrap:wrap;margin-top:20px;"> <button id="btn-cancel-rec" type="button"> ரத்து செய் </button> <button id="btn-upload-rec" type="button" hidden> ☁️ Commons-ல் பதிவேற்று </button> </div> </div> </div>`; $('body').append(html); $('#rec-word').text(currentWord); styleButton($('#btn-start-rec'), '#218838'); styleButton($('#btn-stop-rec'), '#c82333'); styleButton($('#btn-cancel-rec'), '#6c757d'); styleButton($('#btn-upload-rec'), '#0069d9'); $('#btn-start-rec').on('click', startRecording); $('#btn-stop-rec').on('click', stopRecording); $('#btn-cancel-rec').on('click', closeModal); $('#btn-upload-rec').on('click', uploadRecording); } async function startRecording() { const $start = $('#btn-start-rec'); $start.prop('disabled', true); try { setStatus('OGG பதிவு வசதி ஏற்றப்படுகிறது...', '#0056b3'); const OpusMediaRecorder = await loadOggRecorder(); if (!navigator.mediaDevices?.getUserMedia) { throw new Error( 'ஒலிவாங்கி வசதி கிடைக்கவில்லை. HTTPS இணைப்பில் திறக்கவும்.' ); } activeStream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } }); audioChunks = []; recordedBlob = null; /* * Use OpusMediaRecorder to encode OGG directly. * This avoids the FFmpeg SharedArrayBuffer dependency. */ recorder = new OpusMediaRecorder( activeStream, { mimeType: 'audio/ogg', audioBitsPerSecond: 64000 }, getWorkerOptions() ); recorder.ondataavailable = function (event) { if (event.data && event.data.size > 0) { audioChunks.push(event.data); } }; recorder.onerror = function (event) { console.error('Recorder error:', event); setStatus( 'ஒலிப்பதிவு பிழை: ' + errorMessage(event.error || event), 'red' ); }; recorder.onstop = handleRecordingStopped; recorder.start(); $start.hide(); $('#btn-stop-rec').prop('hidden', false).show(); $('#btn-upload-rec').prop('hidden', true).hide(); setStatus( 'பதிவு நடைபெறுகிறது... சொல்லைத் தெளிவாகப் பேசவும்.', '#c62828' ); } catch (error) { console.error(error); stopMicrophone(); setStatus( 'பதிவைத் தொடங்க முடியவில்லை: ' + errorMessage(error), 'red' ); $start.prop('disabled', false).show(); } } function stopRecording() { if (recorder && recorder.state !== 'inactive') { recorder.stop(); } $('#btn-stop-rec').hide(); $('#btn-start-rec') .prop('disabled', false) .text('🔄 மீண்டும் பதிவு செய்') .show(); setStatus('பதிவை நிறுத்துகிறது...', '#0056b3'); } async function handleRecordingStopped() { stopMicrophone(); try { if (!audioChunks.length) { throw new Error( 'ஒலித் தரவு கிடைக்கவில்லை. மீண்டும் பதிவு செய்யவும்.' ); } const recorderMime = ( recorder && recorder.mimeType ? recorder.mimeType : audioChunks[0].type ).toLowerCase(); const rawBlob = new Blob(audioChunks, { type: recorderMime || 'audio/ogg' }); if (!rawBlob.size) { throw new Error('ஒலிப்பதிவு காலியாக உள்ளது.'); } /* * Do not rename WebM bytes as .ogg. * Accept only actual OGG output. */ if ( !rawBlob.type.includes('ogg') && !rawBlob.type.includes('opus') ) { throw new Error( 'பதிவாளர் OGG வடிவத்தை உருவாக்கவில்லை. ' + 'பெறப்பட்ட வடிவம்: ' + rawBlob.type ); } recordedBlob = new Blob([rawBlob], { type: 'audio/ogg' }); releasePreview(); previewUrl = URL.createObjectURL(recordedBlob); $('#rec-preview') .attr('src', previewUrl) .show(); $('#btn-upload-rec') .prop('disabled', false) .prop('hidden', false) .text('☁️ Commons-ல் பதிவேற்று') .show(); setStatus( 'OGG பதிவு தயார் (' + Math.ceil(recordedBlob.size / 1024) + ' KB). ஒலியைக் கேட்டுப் பார்த்து பதிவேற்றவும்.', 'green' ); } catch (error) { console.error(error); recordedBlob = null; $('#btn-upload-rec').hide(); setStatus( 'OGG பதிவு பிழை: ' + errorMessage(error), 'red' ); } } async function uploadRecording() { if ( !recordedBlob || recordedBlob.type !== 'audio/ogg' || recordedBlob.size === 0 ) { setStatus( 'பதிவேற்றத்துக்கு செல்லுபடியான OGG கோப்பு தேவை.', 'red' ); return; } const $button = $('#btn-upload-rec') .prop('disabled', true) .text('பதிவேற்றப்படுகிறது...'); try { setStatus( 'Commons-ல் ஏற்கெனவே கோப்பு உள்ளதா எனச் சரிபார்க்கிறது...', '#0056b3' ); const fileName = `Ta-${currentWord}.ogg`; const commonsApi = new mw.ForeignApi( COMMONS_API_URL ); const user = await commonsApi.get({ action: 'query', meta: 'userinfo', format: 'json' }); if ( !user.query || !user.query.userinfo || user.query.userinfo.anon !== undefined ) { throw new Error( 'Wikimedia Commons-ல் உள்நுழைந்து மீண்டும் முயற்சிக்கவும்.' ); } const existingResult = await commonsApi.get({ action: 'query', titles: `File:${fileName}`, formatversion: 2, format: 'json' }); const existingPage = existingResult?.query?.pages?.[0]; if (existingPage && !existingPage.missing) { throw new Error( `File:${fileName} ஏற்கெனவே உள்ளது. ` + 'ஏற்கெனவே உள்ள கோப்பைச் சரிபார்த்து இணைக்கவும்.' ); } setStatus( 'OGG கோப்பு Commons-ல் பதிவேற்றப்படுகிறது...', '#0056b3' ); const description = [ '== {{int:filedesc}} ==', '{{Information', `|description={{ta|தமிழ்ச் சொல்: ${currentWord} என்பதன் ஒலிப்பு}}`, `|date=${new Date().toISOString().slice(0, 10)}`, '|source={{own}}', '|author=Neyakkoo', '}}', '', '== {{int:license-header}} ==', '{{CC-BY-SA-4.0}}', '', '[[Category:Tamil pronunciation]]' ].join('\n'); const uploadResult = await commonsApi.upload( recordedBlob, { filename: fileName, comment: `Tamil pronunciation audio for ${currentWord}`, text: description, format: 'json' } ); if (uploadResult?.upload?.result !== 'Success') { throw new Error( uploadResult?.error?.info || 'Commons பதிவேற்றம் வெற்றியடையவில்லை.' ); } setStatus( 'Commons பதிவேற்றம் வெற்றி. விக்சனரிப் பக்கத்தைத் திருத்துகிறது...', 'green' ); await updateWiktionaryPage( fileName, new mw.Api() ); setStatus( 'ஒலிப்புக் கோப்பு பதிவேற்றப்பட்டு விக்சனரிப் பக்கத்தில் இணைக்கப்பட்டது.', 'green' ); setTimeout(closeModal, 1500); setTimeout(() => location.reload(), 1800); } catch (error) { console.error('Upload/link error:', error); setStatus( 'பிழை: ' + errorMessage(error), 'red' ); $button .prop('disabled', false) .text('☁️ மீண்டும் முயற்சி செய்'); } } async function updateWiktionaryPage(fileName, api) { const result = await api.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); const page = result?.query?.pages?.[0]; if (!page || page.missing) { throw new Error( 'விக்சனரிப் பக்கத்தைக் கண்டறிய முடியவில்லை.' ); } const revision = page.revisions?.[0]; if (!revision) { throw new Error( 'பக்கத்தின் தற்போதைய உள்ளடக்கத்தைப் பெற முடியவில்லை.' ); } let content = revision.content || ''; if ( /\{\{\s*(ஒலிக்கோப்பு-தமிழ்|audio|ஒலிப்பு)\b/i.test(content) ) { throw new Error( 'இந்தப் பக்கத்தில் ஒலிப்பு வார்ப்புரு ஏற்கெனவே உள்ளது. ' + 'பதிவேற்றிய கோப்பை கைமுறையாகச் சரிபார்க்கவும்: ' + fileName ); } const template = `${AUDIO_TEMPLATE}\n`; const languageHeading = /(^==\s*\{\{\s*மொழி\s*\|\s*ta\s*\}\}\s*==\s*)/m; if (languageHeading.test(content)) { content = content.replace( languageHeading, `$1\n${template}` ); } else { content = `=={{மொழி|ta}}==\n\n${template}\n${content}`; } await api.postWithToken('csrf', { action: 'edit', title: currentWord, text: content, summary: `ஒலிப்புக் கோப்பு இணைப்பு: ${fileName}`, format: 'json' }); } /* Check the current page and add the recorder button. */ async function initialize() { try { const api = new mw.Api(); const result = await api.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: currentWord, formatversion: 2 }); const page = result?.query?.pages?.[0]; if (!page || page.missing) return; const content = page.revisions?.[0]?.content || ''; if ( /\{\{\s*(ஒலிக்கோப்பு-தமிழ்|audio|ஒலிப்பு)\b/i.test(content) ) { return; } showRecordButton(); } catch (error) { console.error( 'விக்சனரி பக்கத்தைச் சரிபார்ப்பதில் பிழை:', error ); showRecordButton(); } } $(initialize); })(); g85fujk1usnwwjqhmrtcnbu8jhqa5gq 2004696 2004695 2026-10-26T23:20:24Z Neyakkoo 16078 பயிற்சி 2004696 javascript text/javascript /* ============================================================ * Tamil Wiktionary Audio Recorder & Linker * Output format: OGG / Opus * Uses opus-media-recorder without direct cross-origin Worker * ============================================================ */ (function () { 'use strict'; if (!window.mw || !window.$) return; if (mw.config.get('wgNamespaceNumber') !== 0) return; const WORD = mw.config.get('wgTitle'); if (!/[\u0B80-\u0BFF]/.test(WORD)) return; const COMMONS_API = 'https://commons.wikimedia.org/w/api.php'; const CDN = 'https://cdn.jsdelivr.net/npm/opus-media-recorder@0.8.0'; const AUDIO_TEMPLATE = '{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}'; let recorder = null; let stream = null; let chunks = []; let oggBlob = null; let previewURL = null; let libraryPromise = null; let currentPageText = ''; function status(message, color = '#333') { $('#rec-status') .text(message) .css('color', color); } function errorText(error) { if (!error) return 'தெரியாத பிழை'; if (error.info) return error.info; if (error.message) return error.message; return String(error); } function loadExternalScript(url) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = url; script.async = true; script.onload = () => resolve(); script.onerror = () => reject( new Error('நூலகத்தை ஏற்ற முடியவில்லை: ' + url) ); document.head.appendChild(script); }); } /* * IMPORTANT: * Load both UMD files as scripts. * Do not create a Worker directly from the CDN URL. */ async function loadOggLibrary() { if ( window.OpusMediaRecorder && window.OpusMediaRecorder.encoderWorker ) { return window.OpusMediaRecorder; } if (libraryPromise) return libraryPromise; libraryPromise = (async () => { if (!window.OpusMediaRecorder) { await loadExternalScript( CDN + '/OpusMediaRecorder.umd.js' ); } if (!window.OpusMediaRecorder) { throw new Error( 'OpusMediaRecorder நூலகம் கிடைக்கவில்லை.' ); } if (!window.OpusMediaRecorder.encoderWorker) { await loadExternalScript( CDN + '/encoderWorker.umd.js' ); } if (!window.OpusMediaRecorder.encoderWorker) { throw new Error( 'Encoder Worker ஏற்றப்படவில்லை. ' + 'CDN அல்லது விக்சனரியின் பாதுகாப்பு அமைப்பைச் சரிபார்க்கவும்.' ); } return window.OpusMediaRecorder; })(); try { return await libraryPromise; } catch (error) { libraryPromise = null; throw error; } } function workerOptions() { /* * encoderWorker.umd.js has already been loaded. * The library supplies its own encoderWorker factory. */ return { OggOpusEncoderWasmPath: CDN + '/OggOpusEncoder.wasm', WebMOpusEncoderWasmPath: CDN + '/WebMOpusEncoder.wasm' }; } function stopStream() { if (stream) { stream.getTracks().forEach(track => track.stop()); stream = null; } } function clearPreview() { if (previewURL) { URL.revokeObjectURL(previewURL); previewURL = null; } } function closeModal() { try { if (recorder && recorder.state !== 'inactive') { recorder.stop(); } } catch (error) { console.warn(error); } stopStream(); clearPreview(); $('#wikt-audio-modal').remove(); recorder = null; oggBlob = null; } function styleButton($button, background) { $button.css({ padding: '8px 12px', margin: '4px', border: '0', borderRadius: '5px', background: background, color: '#fff', cursor: 'pointer', fontSize: '13px' }); } async function initialize() { try { const api = new mw.Api(); const response = await api.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: WORD, formatversion: 2 }); const page = response?.query?.pages?.[0]; if (!page || page.missing) return; currentPageText = page.revisions?.[0]?.content || ''; if (hasAudioTemplate(currentPageText)) return; addRecordButton(); } catch (error) { console.error('பக்கத்தைச் சரிபார்க்கும் பிழை:', error); addRecordButton(); } } function hasAudioTemplate(text) { return /\{\{\s*(ஒலிக்கோப்பு-தமிழ்|audio|ஒலிப்பு)\b/i .test(text || ''); } function addRecordButton() { if ($('#wikt-audio-record-button').length) return; const $button = $('<button>', { id: 'wikt-audio-record-button', type: 'button' }).text('🎙️ ஒலிப்பதிவு செய்'); styleButton($button, '#b83232'); $button.on('click', openModal); $('#firstHeading').append($button); } function openModal() { $('#wikt-audio-modal').remove(); chunks = []; oggBlob = null; const html = ` <div id="wikt-audio-modal" style="position:fixed;inset:0;z-index:99999; background:rgba(0,0,0,.65);display:flex; align-items:center;justify-content:center;padding:14px;"> <div style="background:#fff;color:#222;border-radius:9px; padding:22px;width:min(470px,96vw); box-shadow:0 4px 20px rgba(0,0,0,.3);"> <h2 style="text-align:center;margin-top:0;"> 🎙️ ஒலிப்பதிவு: <span id="audio-word"></span> </h2> <p style="text-align:center;font-size:14px;"> சொல்லைத் தெளிவாகப் பேசிப் பதிவு செய்யவும். பதிவேற்றப்படும் கோப்பு OGG வடிவில் இருக்க வேண்டும். </p> <div style="text-align:center;margin:18px 0;"> <button id="audio-start" type="button"> ▶️ பதிவு தொடங்கு </button> <button id="audio-stop" type="button" hidden> ⏹️ நிறுத்து </button> </div> <audio id="audio-preview" controls style="display:none;width:100%;margin:12px 0;"> </audio> <div id="rec-status" role="status" style="font-size:13px;text-align:center; font-weight:bold;overflow-wrap:anywhere;"> </div> <div style="display:flex;justify-content:space-between; flex-wrap:wrap;margin-top:20px;"> <button id="audio-cancel" type="button"> ரத்து செய் </button> <button id="audio-upload" type="button" hidden> ☁️ Commons-ல் பதிவேற்று </button> </div> </div> </div>`; $('body').append(html); $('#audio-word').text(WORD); styleButton($('#audio-start'), '#218838'); styleButton($('#audio-stop'), '#c82333'); styleButton($('#audio-cancel'), '#6c757d'); styleButton($('#audio-upload'), '#0069d9'); $('#audio-start').on('click', startRecording); $('#audio-stop').on('click', stopRecording); $('#audio-cancel').on('click', closeModal); $('#audio-upload').on('click', uploadRecording); } async function startRecording() { const $start = $('#audio-start').prop('disabled', true); try { status('OGG பதிவு வசதி ஏற்றப்படுகிறது...', '#0056b3'); const OpusRecorder = await loadOggLibrary(); if (!navigator.mediaDevices?.getUserMedia) { throw new Error( 'ஒலிவாங்கி வசதி கிடைக்கவில்லை. HTTPS இணைப்பைப் பயன்படுத்தவும்.' ); } stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } }); chunks = []; oggBlob = null; recorder = new OpusRecorder( stream, { mimeType: 'audio/ogg', audioBitsPerSecond: 64000 }, workerOptions() ); recorder.ondataavailable = event => { if (event.data && event.data.size > 0) { chunks.push(event.data); } }; recorder.onerror = event => { console.error('Recorder error:', event); status( 'ஒலிப்பதிவுப் பிழை: ' + errorText(event.error || event), 'red' ); }; recorder.onstop = finishRecording; recorder.start(); $start.hide(); $('#audio-stop').prop('hidden', false).show(); $('#audio-upload').prop('hidden', true).hide(); status( 'பதிவு நடைபெறுகிறது... சொல்லைத் தெளிவாகப் பேசவும்.', '#c62828' ); } catch (error) { console.error(error); stopStream(); status( 'பதிவைத் தொடங்க முடியவில்லை: ' + errorText(error), 'red' ); $start.prop('disabled', false).show(); } } function stopRecording() { if (recorder && recorder.state !== 'inactive') { recorder.stop(); } $('#audio-stop').hide(); $('#audio-start') .prop('disabled', false) .text('🔄 மீண்டும் பதிவு செய்') .show(); status('பதிவை நிறுத்துகிறது...', '#0056b3'); } async function finishRecording() { stopStream(); try { if (!chunks.length) { throw new Error('ஒலித் தரவு கிடைக்கவில்லை.'); } const mime = ( recorder?.mimeType || chunks[0].type || '' ).toLowerCase(); if (!mime.includes('ogg')) { throw new Error( 'OGG வடிவ ஒலி கிடைக்கவில்லை. பெறப்பட்ட வடிவம்: ' + (mime || 'தெரியவில்லை') ); } oggBlob = new Blob(chunks, { type: 'audio/ogg' }); if (!oggBlob.size) { throw new Error('ஒலிப்பதிவு காலியாக உள்ளது.'); } clearPreview(); previewURL = URL.createObjectURL(oggBlob); $('#audio-preview') .attr('src', previewURL) .show(); $('#audio-upload') .prop('disabled', false) .prop('hidden', false) .text('☁️ Commons-ல் பதிவேற்று') .show(); status( 'OGG பதிவு தயார் (' + Math.ceil(oggBlob.size / 1024) + ' KB). ஒலியைக் கேட்டு உறுதிப்படுத்தவும்.', 'green' ); } catch (error) { console.error(error); oggBlob = null; $('#audio-upload').hide(); status( 'OGG பதிவு பிழை: ' + errorText(error), 'red' ); } } async function uploadRecording() { if ( !oggBlob || oggBlob.type !== 'audio/ogg' || !oggBlob.size ) { status( 'பதிவேற்றத்திற்கு செல்லுபடியான OGG கோப்பு தேவை.', 'red' ); return; } const $button = $('#audio-upload') .prop('disabled', true) .text('பதிவேற்றப்படுகிறது...'); try { const commons = new mw.ForeignApi(COMMONS_API); status( 'Commons கணக்கையும் கோப்புப் பெயரையும் சரிபார்க்கிறது...', '#0056b3' ); const userResult = await commons.get({ action: 'query', meta: 'userinfo', format: 'json' }); if ( !userResult.query?.userinfo || userResult.query.userinfo.anon !== undefined ) { throw new Error( 'Wikimedia Commons-ல் உள்நுழையவும்.' ); } const fileName = `Ta-${WORD}.ogg`; const titleResult = await commons.get({ action: 'query', titles: `File:${fileName}`, formatversion: 2, format: 'json' }); const existing = titleResult.query?.pages?.[0]; if (existing && !existing.missing) { throw new Error( `File:${fileName} ஏற்கெனவே Commons-ல் உள்ளது. ` + 'அதைச் சரிபார்த்து விக்சனரிப் பக்கத்தில் இணைக்கவும்.' ); } status( 'OGG கோப்பு Commons-ல் பதிவேற்றப்படுகிறது...', '#0056b3' ); const description = [ '== {{int:filedesc}} ==', '{{Information', `|description={{ta|${WORD} என்ற சொல்லின் ஒலிப்பு}}`, `|date=${new Date().toISOString().slice(0, 10)}`, '|source={{own}}', '|author=[[User:Neyakkoo|Neyakkoo]]', '}}', '', '== {{int:license-header}} ==', '{{CC-BY-SA-4.0}}', '', '[[Category:Tamil pronunciation]]' ].join('\n'); const uploadResult = await commons.upload( oggBlob, { filename: fileName, comment: `Tamil pronunciation audio: ${WORD}`, text: description, format: 'json' } ); if (uploadResult?.upload?.result !== 'Success') { throw new Error( uploadResult?.error?.info || 'Commons பதிவேற்றம் வெற்றியடையவில்லை.' ); } status( 'பதிவேற்றம் வெற்றி. விக்சனரிப் பக்கத்தை இணைக்கிறது...', 'green' ); await updateWiktionary(fileName); status( 'OGG கோப்பு பதிவேற்றப்பட்டு பக்கத்தில் இணைக்கப்பட்டது.', 'green' ); setTimeout(closeModal, 1500); setTimeout(() => location.reload(), 1800); } catch (error) { console.error('Upload/link error:', error); status( 'பிழை: ' + errorText(error), 'red' ); $button .prop('disabled', false) .text('☁️ மீண்டும் முயற்சி செய்'); } } async function updateWiktionary(fileName) { const api = new mw.Api(); const response = await api.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: WORD, formatversion: 2 }); const page = response.query?.pages?.[0]; if (!page || page.missing) { throw new Error('விக்சனரிப் பக்கத்தைக் கண்டறிய முடியவில்லை.'); } const revision = page.revisions?.[0]; if (!revision) { throw new Error('பக்க உள்ளடக்கத்தைப் பெற முடியவில்லை.'); } let text = revision.content || ''; if (hasAudioTemplate(text)) { throw new Error( 'இந்தப் பக்கத்தில் ஒலிப்பு வார்ப்புரு ஏற்கெனவே உள்ளது. ' + 'பதிவேற்றிய கோப்பை கைமுறையாக இணைக்கவும்: ' + fileName ); } const template = `${AUDIO_TEMPLATE}\n`; const tamilHeading = /(^==\s*\{\{\s*மொழி\s*\|\s*ta\s*\}\}\s*==\s*)/m; if (tamilHeading.test(text)) { text = text.replace( tamilHeading, `$1\n${template}` ); } else { text = `=={{மொழி|ta}}==\n\n${template}\n${text}`; } await api.postWithToken('csrf', { action: 'edit', title: WORD, text: text, summary: `ஒலிப்புக் கோப்பு இணைப்பு: ${fileName}`, format: 'json' }); } $(initialize); })(); 2j5zbdpmu7alyazhyrj9ee5hn0bh5ea 2004698 2004696 2026-10-26T23:30:36Z Neyakkoo 16078 பயிற்சி 2004698 javascript text/javascript /* ============================================================ * Tamil Wiktionary OGG Audio Recorder & Linker — Enhanced * Features: * 1. Detect existing Commons audio for the exact word filename * 2. Verify Commons file and Wiktionary template after upload * 3. Batch recording queue for multiple Tamil words * 4. Dashboard and browser-local progress history * * Install in ta.wiktionary.org/wiki/MediaWiki:Common.js * or your personal common.js. Test before wider deployment. * ============================================================ */ (function () { 'use strict'; if (!window.mw || !window.$) return; if (mw.config.get('wgNamespaceNumber') !== 0) return; const COMMONS_API = 'https://commons.wikimedia.org/w/api.php'; const CDN = 'https://cdn.jsdelivr.net/npm/opus-media-recorder@0.8.0'; const AUDIO_TEMPLATE = '{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}'; const HISTORY_KEY = 'taWiktAudioRecorderHistoryV1'; const QUEUE_KEY = 'taWiktAudioRecorderQueueV1'; let recorder = null; let stream = null; let chunks = []; let oggBlob = null; let previewURL = null; let libraryPromise = null; let queue = []; let queueIndex = 0; let queueMode = false; let currentWord = mw.config.get('wgTitle'); let currentPageText = ''; let isBusy = false; if (!/^[\u0B80-\u0BFF\u0BE6-\u0BEF\s\-]+$/.test(currentWord)) { if (!/[\u0B80-\u0BFF]/.test(currentWord)) return; } function errorText(error) { if (!error) return 'தெரியாத பிழை'; if (error.info) return error.info; if (error.message) return error.message; return String(error); } function setStatus(message, color) { $('#ta-audio-status').text(message).css('color', color || '#333'); } function readJSON(key, fallback) { try { const value = localStorage.getItem(key); return value ? JSON.parse(value) : fallback; } catch (_) { return fallback; } } function writeJSON(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); } catch (_) {} } function getHistory() { return readJSON(HISTORY_KEY, []); } function recordHistory(word, result, fileName, note) { const history = getHistory(); history.unshift({ word: word, result: result, fileName: fileName || `Ta-${word}.ogg`, note: note || '', time: new Date().toISOString() }); writeJSON(HISTORY_KEY, history.slice(0, 1000)); renderDashboard(); } function safeFileName(word) { return `Ta-${word}.ogg`; } function hasAudioTemplate(text) { return /\{\{\s*(ஒலிக்கோப்பு-தமிழ்|audio|ஒலிப்பு)\b/i.test(text || ''); } function loadExternalScript(url) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = url; script.async = true; script.onload = resolve; script.onerror = () => reject( new Error('நூலகத்தை ஏற்ற முடியவில்லை: ' + url) ); document.head.appendChild(script); }); } async function loadOggLibrary() { if ( window.OpusMediaRecorder && window.OpusMediaRecorder.encoderWorker ) { return window.OpusMediaRecorder; } if (libraryPromise) return libraryPromise; libraryPromise = (async () => { if (!window.OpusMediaRecorder) { await loadExternalScript( CDN + '/OpusMediaRecorder.umd.js' ); } if (!window.OpusMediaRecorder) { throw new Error( 'OpusMediaRecorder நூலகம் கிடைக்கவில்லை.' ); } if (!window.OpusMediaRecorder.encoderWorker) { await loadExternalScript( CDN + '/encoderWorker.umd.js' ); } if (!window.OpusMediaRecorder.encoderWorker) { throw new Error('Encoder Worker ஏற்றப்படவில்லை.'); } return window.OpusMediaRecorder; })(); try { return await libraryPromise; } catch (error) { libraryPromise = null; throw error; } } function workerOptions() { return { OggOpusEncoderWasmPath: CDN + '/OggOpusEncoder.wasm', WebMOpusEncoderWasmPath: CDN + '/WebMOpusEncoder.wasm' }; } function stopStream() { if (stream) { stream.getTracks().forEach(track => track.stop()); stream = null; } } function clearPreview() { if (previewURL) { URL.revokeObjectURL(previewURL); previewURL = null; } } function styleButton($button, background) { $button.css({ padding: '7px 11px', margin: '3px', border: '0', borderRadius: '5px', background: background, color: '#fff', cursor: 'pointer', fontSize: '13px' }); } function addToolbarButtons() { if (!$('#ta-audio-toolbar').length) { const $toolbar = $('<span>', { id: 'ta-audio-toolbar' }).css({ display: 'inline-flex', flexWrap: 'wrap', gap: '3px', marginLeft: '8px' }); $('#firstHeading').append($toolbar); const $single = $('<button>', { type: 'button' }).text('🎙️ ஒலிப்பதிவு'); styleButton($single, '#b83232'); $single.on('click', async () => { try { const check = await inspectWord(currentWord); if (check.fileExists && check.pageHasTemplate) { showNotice( `இந்தச் சொல்லுக்கான OGG கோப்பும் பக்க இணைப்பும் ஏற்கெனவே உள்ளன: ${check.fileName}`, 'green' ); return; } if (check.fileExists) { showExistingFileAction( currentWord, check.fileName ); return; } queue = [currentWord]; queueIndex = 0; queueMode = false; openRecorderModal(); } catch (error) { showNotice(errorText(error), 'red'); } }); const $batch = $('<button>', { type: 'button' }).text('📝 பல சொற்கள்'); styleButton($batch, '#1769aa'); $batch.on('click', openBatchDialog); const $dash = $('<button>', { type: 'button' }).text('📊 Dashboard'); styleButton($dash, '#5e548e'); $dash.on('click', openDashboard); $toolbar.append($single, $batch, $dash); } } function showNotice(message, color) { $('#ta-audio-notice').remove(); const $notice = $('<div>', { id: 'ta-audio-notice' }).text(message).css({ margin: '8px 0', padding: '8px 10px', border: '1px solid #ccc', borderRadius: '5px', color: color || '#333', fontSize: '13px' }); $('#firstHeading').after($notice); setTimeout(() => { $notice.fadeOut(500, () => $notice.remove()); }, 9000); } async function getPageData(word) { const api = new mw.Api(); const response = await api.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: word, formatversion: 2 }); const page = response?.query?.pages?.[0]; return { exists: !!page && !page.missing, text: page?.revisions?.[0]?.content || '' }; } async function commonsFileExists(fileName) { const api = new mw.ForeignApi(COMMONS_API); const response = await api.get({ action: 'query', titles: `File:${fileName}`, formatversion: 2, prop: 'imageinfo', iiprop: 'url|mime|size' }); const page = response?.query?.pages?.[0]; if (!page || page.missing) { return { exists: false, page: null }; } const info = page.imageinfo?.[0] || {}; return { exists: true, page: page, url: info.url || `https://commons.wikimedia.org/wiki/${encodeURIComponent('File:' + fileName)}`, mime: info.mime || '', size: info.size || 0 }; } async function inspectWord(word) { const fileName = safeFileName(word); const [pageData, fileData] = await Promise.all([ getPageData(word), commonsFileExists(fileName) ]); return { word: word, fileName: fileName, pageExists: pageData.exists, pageText: pageData.text, pageHasTemplate: hasAudioTemplate(pageData.text), fileExists: fileData.exists, fileUrl: fileData.url || '', mime: fileData.mime || '', fileSize: fileData.size || 0 }; } function showExistingFileAction(word, fileName) { $('#ta-audio-existing').remove(); const $box = $('<div>', { id: 'ta-audio-existing' }).css({ padding: '10px', margin: '8px 0', border: '1px solid #d7b45a', borderRadius: '5px', background: '#fff9e6' }); $box.append( $('<p>').text( `Commons-ல் ${fileName} ஏற்கெனவே உள்ளது.` ) ); const $link = $('<a>', { href: 'https://commons.wikimedia.org/wiki/' + encodeURIComponent('File:' + fileName), target: '_blank', rel: 'noopener noreferrer' }).text('Commons கோப்பைப் பார்க்கவும்'); const $linkButton = $('<button>', { type: 'button' }).text('பக்கத்தில் ஒலிப்பு வார்ப்புருவை இணைக்கவும்'); styleButton($linkButton, '#1769aa'); $linkButton.on('click', async () => { $linkButton .prop('disabled', true) .text('சரிபார்க்கிறது...'); try { const check = await inspectWord(word); if (check.pageHasTemplate) { $linkButton.text('ஏற்கெனவே இணைக்கப்பட்டுள்ளது'); recordHistory( word, 'already-linked', fileName, 'Existing file and template' ); } else { await updateWiktionary(word, fileName); const verify = await verifyWiktionaryLink(word); if (!verify.ok) { throw new Error( 'பக்க இணைப்பை உறுதிப்படுத்த முடியவில்லை.' ); } $linkButton.text('இணைக்கப்பட்டது ✓'); recordHistory( word, 'linked-existing', fileName, 'Existing Commons file linked' ); } } catch (error) { $linkButton .prop('disabled', false) .text('மீண்டும் முயற்சி செய்'); showNotice(errorText(error), 'red'); } }); $box.append( $('<div>').append($link, $('<br>'), $linkButton) ); $('#firstHeading').after($box); } function openBatchDialog() { $('#ta-audio-batch-dialog').remove(); const $modal = $('<div>', { id: 'ta-audio-batch-dialog' }).css({ position: 'fixed', inset: 0, zIndex: 100000, background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '15px' }); const $panel = $('<div>').css({ background: '#fff', color: '#222', padding: '20px', borderRadius: '8px', width: 'min(520px,96vw)', maxHeight: '90vh', overflow: 'auto' }); const $textarea = $('<textarea>', { id: 'ta-audio-batch-words', rows: 8, placeholder: 'ஒவ்வொரு வரியிலும் ஒரு தமிழ் சொல் எழுதவும்\nஎ.கா.\nஅப்புதல்\nஅம்மா\nதமிழ்' }).css({ width: '100%', boxSizing: 'border-box', margin: '10px 0' }); const $start = $('<button>', { type: 'button' }).text('வரிசையைத் தொடங்கு'); const $cancel = $('<button>', { type: 'button' }).text('ரத்து செய்'); styleButton($start, '#1769aa'); styleButton($cancel, '#6c757d'); $start.on('click', () => { const words = [...new Set( $textarea.val() .split(/[\n,;]+/) .map(w => w.trim()) .filter(Boolean) )]; if (!words.length) { $('#ta-batch-error') .text('குறைந்தது ஒரு சொல்லையாவது உள்ளிடவும்.') .css('color', 'red'); return; } const invalid = words.filter( w => !/[\u0B80-\u0BFF]/.test(w) ); if (invalid.length) { $('#ta-batch-error') .text( 'தமிழ் எழுத்துகள் உள்ள சொற்களை மட்டும் உள்ளிடவும்: ' + invalid.join(', ') ) .css('color', 'red'); return; } queue = words; queueIndex = 0; queueMode = true; writeJSON(QUEUE_KEY, { words: queue, index: 0, updated: new Date().toISOString() }); $modal.remove(); openRecorderModal(); }); $cancel.on('click', () => $modal.remove()); $panel.append( $('<h2>').text( 'பல சொற்களுக்கான தொடர்ச்சியான ஒலிப்பதிவு' ), $('<p>').text( 'ஒவ்வொரு வரியிலும் ஒரு சொல் எழுதவும். ஏற்கெனவே கோப்பு உள்ள சொற்கள் தவிர்க்கப்படலாம்.' ), $textarea, $('<div>', { id: 'ta-batch-error' }), $start, $cancel ); $modal.append($panel); $('body').append($modal); } function openRecorderModal() { $('#ta-audio-recorder-modal').remove(); chunks = []; oggBlob = null; const word = queue[queueIndex] || currentWord; const $modal = $('<div>', { id: 'ta-audio-recorder-modal' }).css({ position: 'fixed', inset: 0, zIndex: 100001, background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '15px' }); const $panel = $('<div>').css({ background: '#fff', color: '#222', padding: '20px', borderRadius: '9px', width: 'min(480px,96vw)', maxHeight: '90vh', overflow: 'auto' }); const $word = $('<h2>') .css({ textAlign: 'center', marginTop: 0 }) .text(`🎙️ ஒலிப்பதிவு: ${word}`); const $progress = $('<p>') .css({ textAlign: 'center', fontSize: '13px' }) .text( queueMode ? `சொல் ${queueIndex + 1} / ${queue.length}` : 'ஒரு சொல்லுக்கான ஒலிப்பதிவு' ); const $start = $('<button>', { id: 'ta-audio-start', type: 'button' }).text('▶️ பதிவு தொடங்கு'); const $stop = $('<button>', { id: 'ta-audio-stop', type: 'button', hidden: true }).text('⏹️ நிறுத்து'); const $audio = $('<audio>', { id: 'ta-audio-preview', controls: true }).css({ display: 'none', width: '100%', margin: '12px 0' }); const $status = $('<div>', { id: 'ta-audio-status', role: 'status' }).css({ textAlign: 'center', fontWeight: 'bold', fontSize: '13px', overflowWrap: 'anywhere' }); const $upload = $('<button>', { id: 'ta-audio-upload', type: 'button', hidden: true }).text('☁️ Commons-ல் பதிவேற்று'); const $cancel = $('<button>', { id: 'ta-audio-cancel', type: 'button' }).text('ரத்து செய்'); const $skip = $('<button>', { id: 'ta-audio-skip', type: 'button' }).text('இந்தச் சொல்லைத் தவிர்'); styleButton($start, '#218838'); styleButton($stop, '#c82333'); styleButton($upload, '#1769aa'); styleButton($cancel, '#6c757d'); styleButton($skip, '#8a6d3b'); $start.on('click', startRecording); $stop.on('click', stopRecording); $upload.on('click', uploadRecording); $cancel.on('click', () => { stopStream(); clearPreview(); $modal.remove(); if (queueMode) { writeJSON(QUEUE_KEY, { words: queue, index: queueIndex, cancelled: true, updated: new Date().toISOString() }); } }); $skip.on('click', async () => { recordHistory( word, 'skipped', safeFileName(word), 'Skipped by user' ); await moveToNextWord(); }); $panel.append( $word, $progress, $('<p>') .css({ textAlign: 'center', fontSize: '13px' }) .text('பதிவேற்றப்படும் கோப்பு OGG வடிவில் இருக்க வேண்டும்.'), $('<div>') .css({ textAlign: 'center', margin: '16px 0' }) .append($start, $stop), $audio, $status, $('<div>') .css({ display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', marginTop: '18px' }) .append($cancel, $skip, $upload) ); $modal.append($panel); $('body').append($modal); inspectWord(word).then(check => { if (check.fileExists) { setStatus( `Commons-ல் ${check.fileName} ஏற்கெனவே உள்ளது. புதிய கோப்பை உருவாக்கவில்லை.`, '#8a6d3b' ); $start.prop('disabled', true); const $linkExisting = $('<button>', { type: 'button' }).text('ஏற்கெனவே உள்ள கோப்பை இணைக்கவும்'); styleButton($linkExisting, '#1769aa'); $linkExisting.on('click', async () => { $linkExisting .prop('disabled', true) .text('இணைக்கிறது...'); try { if (!check.pageHasTemplate) { await updateWiktionary( word, check.fileName ); } const verification = await verifyAll( word, check.fileName ); if (!verification.ok) { throw new Error(verification.message); } recordHistory( word, 'linked-existing', check.fileName, 'Existing Commons file linked and verified' ); setStatus( 'ஏற்கெனவே உள்ள கோப்பு இணைக்கப்பட்டு சரிபார்க்கப்பட்டது.', 'green' ); setTimeout(moveToNextWord, 900); } catch (error) { setStatus(errorText(error), 'red'); $linkExisting .prop('disabled', false) .text('மீண்டும் முயற்சி செய்'); } }); $status.after($linkExisting); if (check.pageHasTemplate) { setStatus( 'கோப்பும் பக்க இணைப்பும் ஏற்கெனவே உள்ளன; இந்தச் சொல் தவிர்க்கப்படும்.', 'green' ); recordHistory( word, 'already-complete', check.fileName, 'File and page template already exist' ); setTimeout(moveToNextWord, 1300); } } }).catch(error => { console.warn('முன் சரிபார்ப்பு:', error); }); } async function startRecording() { const $start = $('#ta-audio-start') .prop('disabled', true); try { setStatus( 'OGG பதிவு வசதி ஏற்றப்படுகிறது...', '#1769aa' ); const OpusRecorder = await loadOggLibrary(); if (!navigator.mediaDevices?.getUserMedia) { throw new Error( 'ஒலிவாங்கி வசதி கிடைக்கவில்லை. HTTPS இணைப்பைப் பயன்படுத்தவும்.' ); } stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } }); chunks = []; oggBlob = null; recorder = new OpusRecorder( stream, { mimeType: 'audio/ogg', audioBitsPerSecond: 64000 }, workerOptions() ); recorder.ondataavailable = event => { if (event.data && event.data.size) { chunks.push(event.data); } }; recorder.onerror = event => { console.error(event); setStatus( 'பதிவுப் பிழை: ' + errorText(event.error || event), 'red' ); }; recorder.onstop = finishRecording; recorder.start(); $start.hide(); $('#ta-audio-stop') .prop('hidden', false) .show(); $('#ta-audio-upload') .prop('hidden', true) .hide(); setStatus( 'பதிவு நடைபெறுகிறது... சொல்லைத் தெளிவாகப் பேசவும்.', '#c62828' ); } catch (error) { console.error(error); stopStream(); setStatus( 'பதிவைத் தொடங்க முடியவில்லை: ' + errorText(error), 'red' ); $start.prop('disabled', false).show(); } } function stopRecording() { if (recorder && recorder.state !== 'inactive') { recorder.stop(); } $('#ta-audio-stop').hide(); $('#ta-audio-start') .prop('disabled', false) .text('🔄 மீண்டும் பதிவு செய்') .show(); setStatus('பதிவை நிறுத்துகிறது...', '#1769aa'); } async function finishRecording() { stopStream(); try { if (!chunks.length) { throw new Error('ஒலித் தரவு கிடைக்கவில்லை.'); } const mime = ( recorder?.mimeType || chunks[0].type || '' ).toLowerCase(); if (!mime.includes('ogg')) { throw new Error( 'OGG வடிவ ஒலி கிடைக்கவில்லை. பெறப்பட்ட வடிவம்: ' + (mime || 'தெரியவில்லை') ); } oggBlob = new Blob(chunks, { type: 'audio/ogg' }); if (!oggBlob.size) { throw new Error('ஒலிப்பதிவு காலியாக உள்ளது.'); } clearPreview(); previewURL = URL.createObjectURL(oggBlob); $('#ta-audio-preview') .attr('src', previewURL) .show(); $('#ta-audio-upload') .prop('disabled', false) .prop('hidden', false) .show(); setStatus( `OGG பதிவு தயார் (${Math.ceil(oggBlob.size / 1024)} KB). கேட்டுப் பார்த்து பதிவேற்றவும்.`, 'green' ); } catch (error) { console.error(error); oggBlob = null; $('#ta-audio-upload').hide(); setStatus( 'OGG பதிவு பிழை: ' + errorText(error), 'red' ); } } async function uploadRecording() { const word = queue[queueIndex] || currentWord; const fileName = safeFileName(word); if (!oggBlob || oggBlob.type !== 'audio/ogg' || !oggBlob.size) { setStatus('செல்லுபடியான OGG கோப்பு தேவை.', 'red'); return; } if (isBusy) return; isBusy = true; const $button = $('#ta-audio-upload') .prop('disabled', true) .text('பதிவேற்றப்படுகிறது...'); try { const commons = new mw.ForeignApi(COMMONS_API); const userResult = await commons.get({ action: 'query', meta: 'userinfo', format: 'json' }); if ( !userResult.query?.userinfo || userResult.query.userinfo.anon !== undefined ) { throw new Error( 'Wikimedia Commons-ல் உள்நுழையவும்.' ); } const precheck = await commonsFileExists(fileName); if (precheck.exists) { throw new Error( `File:${fileName} ஏற்கெனவே உள்ளது. அதை இணைக்கும் வசதியைப் பயன்படுத்தவும்.` ); } setStatus( 'Commons-ல் OGG கோப்பு பதிவேற்றப்படுகிறது...', '#1769aa' ); const description = [ '== {{int:filedesc}} ==', '{{Information', `|description={{ta|${word} என்ற சொல்லின் ஒலிப்பு}}`, `|date=${new Date().toISOString().slice(0, 10)}`, '|source={{own}}', '|author=[[User:Neyakkoo|Neyakkoo]]', '}}', '', '== {{int:license-header}} ==', '{{CC-BY-SA-4.0}}', '', '[[Category:Tamil pronunciation]]' ].join('\n'); const upload = await commons.upload(oggBlob, { filename: fileName, comment: `Tamil pronunciation audio: ${word}`, text: description, format: 'json' }); if (upload?.upload?.result !== 'Success') { throw new Error( upload?.error?.info || 'Commons பதிவேற்றம் வெற்றியடையவில்லை.' ); } setStatus( 'பதிவேற்றம் வெற்றி. கோப்பையும் விக்சனரி இணைப்பையும் சரிபார்க்கிறது...', '#1769aa' ); await wait(1200); await updateWiktionary(word, fileName); await wait(900); const verification = await verifyAll(word, fileName); if (!verification.ok) { recordHistory( word, 'uploaded-unverified', fileName, verification.message ); throw new Error( 'கோப்பு பதிவேற்றப்பட்டது; ஆனால் இணைப்பு சரிபார்ப்பு தோல்வி: ' + verification.message ); } recordHistory( word, 'uploaded-verified', fileName, 'Commons file and Wiktionary link verified' ); setStatus( 'வெற்றி! OGG கோப்பும் விக்சனரி இணைப்பும் சரிபார்க்கப்பட்டன.', 'green' ); await wait(1000); await moveToNextWord(); } catch (error) { console.error('Upload/link error:', error); setStatus('பிழை: ' + errorText(error), 'red'); $button .prop('disabled', false) .text('☁️ மீண்டும் முயற்சி செய்'); recordHistory( word, 'error', fileName, errorText(error) ); } finally { isBusy = false; } } function wait(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function updateWiktionary(word, fileName) { const api = new mw.Api(); const response = await api.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: word, formatversion: 2 }); const page = response.query?.pages?.[0]; if (!page || page.missing) { throw new Error(`விக்சனரிப் பக்கம் இல்லை: ${word}`); } const revision = page.revisions?.[0]; if (!revision) { throw new Error('பக்க உள்ளடக்கத்தைப் பெற முடியவில்லை.'); } let text = revision.content || ''; if (hasAudioTemplate(text)) return; const template = `${AUDIO_TEMPLATE}\n`; const languageHeading = /(^==\s*\{\{\s*மொழி\s*\|\s*ta\s*\}\}\s*==\s*)/m; if (languageHeading.test(text)) { text = text.replace( languageHeading, `$1\n${template}` ); } else { text = `=={{மொழி|ta}}==\n\n${template}\n${text}`; } await api.postWithToken('csrf', { action: 'edit', title: word, text: text, summary: `ஒலிப்புக் கோப்பு இணைப்பு: ${fileName}`, format: 'json' }); } async function verifyWiktionaryLink(word) { const page = await getPageData(word); return { ok: page.exists && hasAudioTemplate(page.text), pageText: page.text }; } async function verifyAll(word, fileName) { const [file, link] = await Promise.all([ commonsFileExists(fileName), verifyWiktionaryLink(word) ]); const ok = file.exists && link.ok; return { ok: ok, fileExists: file.exists, pageLinked: link.ok, message: !file.exists ? 'Commons கோப்பு காணப்படவில்லை.' : (!link.ok ? 'விக்சனரி பக்கத்தில் ஒலிப்பு வார்ப்புரு இல்லை.' : 'சரிபார்ப்பு வெற்றி') }; } async function moveToNextWord() { stopStream(); clearPreview(); $('#ta-audio-recorder-modal').remove(); recorder = null; oggBlob = null; isBusy = false; if (!queueMode) { renderDashboard(); return; } queueIndex += 1; writeJSON(QUEUE_KEY, { words: queue, index: queueIndex, updated: new Date().toISOString() }); while (queueIndex < queue.length) { const word = queue[queueIndex]; try { const check = await inspectWord(word); if (check.fileExists && check.pageHasTemplate) { recordHistory( word, 'already-complete', check.fileName, 'Skipped: file and template exist' ); queueIndex += 1; writeJSON(QUEUE_KEY, { words: queue, index: queueIndex, updated: new Date().toISOString() }); continue; } if (check.fileExists) { openRecorderModal(); return; } openRecorderModal(); return; } catch (error) { recordHistory( word, 'error', safeFileName(word), errorText(error) ); queueIndex += 1; } } queueMode = false; writeJSON(QUEUE_KEY, { words: queue, index: queueIndex, completed: true, updated: new Date().toISOString() }); showNotice( `தொடர் ஒலிப்பதிவு முடிந்தது. ${queue.length} சொற்கள் செயலாக்கப்பட்டன. Dashboard-ல் விவரங்களைப் பார்க்கவும்.`, 'green' ); renderDashboard(); } function renderDashboard() { const history = getHistory(); const today = new Date().toISOString().slice(0, 10); const uploaded = history.filter( x => x.result === 'uploaded-verified' ).length; const linked = history.filter( x => x.result === 'linked-existing' ).length; const existing = history.filter( x => x.result === 'already-complete' ).length; const skipped = history.filter( x => x.result === 'skipped' ).length; const errors = history.filter( x => x.result === 'error' || x.result === 'uploaded-unverified' ).length; const todayCount = history.filter( x => (x.time || '').startsWith(today) && ( x.result === 'uploaded-verified' || x.result === 'linked-existing' ) ).length; return { history, uploaded, linked, existing, skipped, errors, todayCount, total: history.length }; } function openDashboard() { $('#ta-audio-dashboard').remove(); const data = renderDashboard(); const $modal = $('<div>', { id: 'ta-audio-dashboard' }).css({ position: 'fixed', inset: 0, zIndex: 100002, background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '15px' }); const $panel = $('<div>').css({ background: '#fff', color: '#222', padding: '20px', borderRadius: '9px', width: 'min(760px,97vw)', maxHeight: '90vh', overflow: 'auto' }); const $grid = $('<div>').css({ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(130px,1fr))', gap: '8px' }); const cards = [ ['பதிவேற்றம் + சரிபார்ப்பு', data.uploaded, '#e7f5e8'], ['ஏற்கெனவே இருந்த கோப்பு இணைப்பு', data.linked, '#e8f1ff'], ['முன்பே முழுமை', data.existing, '#f0ebff'], ['தவிர்க்கப்பட்டது', data.skipped, '#fff4df'], ['பிழைகள்', data.errors, '#ffe8e8'], ['இன்று இணைத்தவை', data.todayCount, '#e8f7f7'] ]; cards.forEach(([label, value, bg]) => { $grid.append( $('<div>').css({ background: bg, padding: '12px', borderRadius: '7px' }).append( $('<div>').css({ fontSize: '12px' }).text(label), $('<strong>').css({ display: 'block', fontSize: '25px', marginTop: '5px' }).text(value) ) ); }); const $table = $('<table>').css({ width: '100%', borderCollapse: 'collapse', marginTop: '14px', fontSize: '13px' }); const $thead = $('<tr>'); ['சொல்', 'நிலை', 'நேரம்', 'குறிப்பு'].forEach(t => { $thead.append( $('<th>').css({ textAlign: 'left', padding: '7px', borderBottom: '1px solid #ccc' }).text(t) ); }); $table.append($('<thead>').append($thead)); const $tbody = $('<tbody>'); data.history.slice(0, 100).forEach(item => { const date = item.time ? new Date(item.time).toLocaleString() : ''; const $row = $('<tr>'); [ item.word, item.result, date, item.note || '' ].forEach(value => { $row.append( $('<td>').css({ padding: '7px', borderBottom: '1px solid #eee', verticalAlign: 'top' }).text(value) ); }); $tbody.append($row); }); $table.append($tbody); const $export = $('<button>', { type: 'button' }).text('CSV அறிக்கையைப் பதிவிறக்கு'); const $clear = $('<button>', { type: 'button' }).text('உள்ளூர் வரலாற்றை அழி'); const $close = $('<button>', { type: 'button' }).text('மூடு'); styleButton($export, '#1769aa'); styleButton($clear, '#a33'); styleButton($close, '#6c757d'); $export.on('click', () => { const rows = [ ['word', 'result', 'fileName', 'time', 'note'], ...data.history.map(x => [ x.word, x.result, x.fileName, x.time, x.note ]) ]; const csv = rows.map(row => row.map(v => '"' + String(v || '').replace(/"/g, '""') + '"' ).join(',') ).join('\r\n'); const blob = new Blob( ['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' } ); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'tamil-wiktionary-audio-progress.csv'; a.click(); URL.revokeObjectURL(url); }); $clear.on('click', () => { if (confirm( 'இந்த உலாவியில் சேமிக்கப்பட்ட Dashboard வரலாற்றை அழிக்கவா? Commons கோப்புகள் அழிக்கப்படாது.' )) { writeJSON(HISTORY_KEY, []); $modal.remove(); openDashboard(); } }); $close.on('click', () => $modal.remove()); $panel.append( $('<h2>').text('📊 தமிழ் விக்சனரி ஒலிப்பதிவு Dashboard'), $('<p>').text( `இந்த உலாவியில் சேமிக்கப்பட்ட வரலாறு: ${data.total} பதிவுகள். இது உள்ளூர் Dashboard; மற்ற கணினிகள் அல்லது உலாவிகளின் வரலாறு இதில் தானாகச் சேராது.` ), $grid, $('<h3>').text('சமீபத்திய செயல்பாடுகள்'), $table, $export, $clear, $close ); $modal.append($panel); $('body').append($modal); } $(function () { addToolbarButtons(); inspectWord(currentWord).then(check => { if (check.fileExists && check.pageHasTemplate) { showNotice( `இந்தச் சொல்லுக்கான ஒலிப்புக் கோப்பும் இணைப்பும் ஏற்கெனவே உள்ளன (${check.fileName}).`, 'green' ); } else if (check.fileExists) { showNotice( `Commons-ல் ${check.fileName} உள்ளது; விக்சனரி பக்கத்தில் இணைப்பு இல்லாமல் இருக்கலாம்.`, '#8a6d3b' ); } }).catch(error => { console.warn('Audio precheck failed:', error); }); }); })(); 5ema9s4hzi0xhvekjm2mjb8ejrvkp67 2004701 2004698 2026-10-26T23:40:35Z Neyakkoo 16078 பயிற்சி 2004701 javascript text/javascript /* ============================================================ * Tamil Wiktionary Audio Recorder — Category Batch Edition * OGG recording, category import, duplicate detection, * template-safe editing, verification and local dashboard. * ============================================================ */ (function () { 'use strict'; if (!window.mw || !window.$) return; if (mw.config.get('wgNamespaceNumber') !== 0) return; const COMMONS_API = 'https://commons.wikimedia.org/w/api.php'; const CDN = 'https://cdn.jsdelivr.net/npm/opus-media-recorder@0.8.0'; const TEMPLATE = '{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}'; const CATEGORY = 'பகுப்பு:தமிழ்-ஒலிக்கோப்புகளில்லை'; const HISTORY_KEY = 'taWiktAudioHistoryV2'; let words = []; let index = 0; let batchMode = false; let recorder = null; let stream = null; let chunks = []; let oggBlob = null; let previewURL = null; let libraryPromise = null; let busy = false; const pageWord = mw.config.get('wgTitle'); if (!/[\u0B80-\u0BFF]/.test(pageWord)) return; function message(text, color = '#333') { $('#ta-audio-status').text(text).css('color', color); } function errorText(error) { return error?.info || error?.message || String(error || 'தெரியாத பிழை'); } function history() { try { return JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]'); } catch (_) { return []; } } function saveHistory(word, status, note = '') { const data = history(); data.unshift({ word, file: `Ta-${word}.ogg`, status, note, time: new Date().toISOString() }); try { localStorage.setItem(HISTORY_KEY, JSON.stringify(data.slice(0, 1000))); } catch (_) {} } function addHistory(word, status, note = '') { saveHistory(word, status, note); renderDashboard(); } function hasTemplate(text) { return /\{\{\s*(ஒலிக்கோப்பு-தமிழ்|audio|ஒலிப்பு)\b/i.test(text || ''); } function fileName(word) { return `Ta-${word}.ogg`; } function buttonStyle($button, color) { $button.css({ padding: '7px 10px', margin: '3px', border: '0', borderRadius: '5px', background: color, color: '#fff', cursor: 'pointer' }); } function notice(text, color = '#333') { $('#ta-audio-notice').remove(); const $box = $('<div>', { id: 'ta-audio-notice' }) .text(text) .css({ padding: '8px', margin: '8px 0', border: '1px solid #ccc', borderRadius: '5px', color }); $('#firstHeading').after($box); } function loadScript(url) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = url; script.onload = resolve; script.onerror = () => reject( new Error('நூலகத்தை ஏற்ற முடியவில்லை: ' + url) ); document.head.appendChild(script); }); } async function loadRecorderLibrary() { if (window.OpusMediaRecorder && window.OpusMediaRecorder.encoderWorker) { return window.OpusMediaRecorder; } if (libraryPromise) return libraryPromise; libraryPromise = (async () => { if (!window.OpusMediaRecorder) { await loadScript(CDN + '/OpusMediaRecorder.umd.js'); } if (!window.OpusMediaRecorder) { throw new Error('OpusMediaRecorder கிடைக்கவில்லை.'); } if (!window.OpusMediaRecorder.encoderWorker) { await loadScript(CDN + '/encoderWorker.umd.js'); } if (!window.OpusMediaRecorder.encoderWorker) { throw new Error('Encoder Worker கிடைக்கவில்லை.'); } return window.OpusMediaRecorder; })(); try { return await libraryPromise; } catch (error) { libraryPromise = null; throw error; } } function workerOptions() { return { OggOpusEncoderWasmPath: CDN + '/OggOpusEncoder.wasm', WebMOpusEncoderWasmPath: CDN + '/WebMOpusEncoder.wasm' }; } function stopStream() { if (stream) { stream.getTracks().forEach(track => track.stop()); stream = null; } } function clearPreview() { if (previewURL) { URL.revokeObjectURL(previewURL); previewURL = null; } } async function getPage(word) { const api = new mw.Api(); const result = await api.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: word, formatversion: 2 }); const page = result?.query?.pages?.[0]; return { exists: !!page && !page.missing, text: page?.revisions?.[0]?.content || '' }; } async function getCommonsFile(name) { const api = new mw.ForeignApi(COMMONS_API); const result = await api.get({ action: 'query', titles: `File:${name}`, prop: 'imageinfo', iiprop: 'url|mime|size', formatversion: 2 }); const page = result?.query?.pages?.[0]; if (!page || page.missing) return { exists: false }; const info = page.imageinfo?.[0] || {}; return { exists: true, url: info.url || '', mime: info.mime || '', size: info.size || 0 }; } async function inspect(word) { const name = fileName(word); const [page, file] = await Promise.all([ getPage(word), getCommonsFile(name) ]); return { word, name, pageExists: page.exists, pageHasTemplate: hasTemplate(page.text), pageText: page.text, fileExists: file.exists, fileUrl: file.url || '', mime: file.mime || '' }; } /* * Fetch pages from the category using MediaWiki continuation. * The limit is deliberately capped at 500 per run. */ async function fetchCategoryWords(limit) { const api = new mw.Api(); const results = []; let continuation = {}; while (results.length < limit) { const response = await api.get({ action: 'query', list: 'categorymembers', cmtitle: CATEGORY, cmtype: 'page', cmnamespace: 0, cmlimit: Math.min(500, limit - results.length), formatversion: 2, ...continuation }); const members = response?.query?.categorymembers || []; results.push(...members.map(item => item.title)); if (!response.continue) break; continuation = response.continue; } return [...new Set(results)]; } async function importCategory() { const answer = prompt( 'பகுப்பு:தமிழ்-ஒலிக்கோப்புகளில்லை என்பதிலிருந்து எத்தனை பக்கங்களை ஏற்ற வேண்டும்? (1–500)', '200' ); if (answer === null) return; const limit = Number(answer); if (!Number.isInteger(limit) || limit < 1 || limit > 500) { alert('1 முதல் 500 வரையிலான முழு எண்ணை உள்ளிடவும்.'); return; } try { notice('பகுப்பிலிருந்து பக்கங்கள் பெறப்படுகின்றன...', '#1769aa'); words = await fetchCategoryWords(limit); index = 0; batchMode = true; if (!words.length) { notice('இந்தப் பகுப்பில் பக்கங்கள் கிடைக்கவில்லை.', '#a66b00'); return; } notice(`${words.length} பக்கங்கள் ஏற்றப்பட்டன. ஒவ்வொரு பக்கமும் முன்கூட்டியே சரிபார்க்கப்படும்.`, 'green'); await nextWord(); } catch (error) { console.error(error); notice('பகுப்பைப் பெற முடியவில்லை: ' + errorText(error), 'red'); } } function importManualWords() { const text = prompt( 'ஒவ்வொரு வரியிலும் ஒரு தமிழ் சொல்லை உள்ளிடவும்:' ); if (text === null) return; words = [...new Set( text.split(/[\n,;]+/) .map(value => value.trim()) .filter(value => /[\u0B80-\u0BFF]/.test(value)) )]; if (!words.length) { notice('தமிழ்ச் சொற்கள் கிடைக்கவில்லை.', 'red'); return; } index = 0; batchMode = true; nextWord(); } function openRecorder(word) { $('#ta-audio-recorder').remove(); chunks = []; oggBlob = null; const $modal = $('<div>', { id: 'ta-audio-recorder' }).css({ position: 'fixed', inset: 0, zIndex: 100001, background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '12px' }); const $panel = $('<div>').css({ background: '#fff', color: '#222', padding: '20px', borderRadius: '8px', width: 'min(470px,96vw)', maxHeight: '90vh', overflow: 'auto' }); const $start = $('<button>', { type: 'button' }) .text('▶️ பதிவு தொடங்கு'); const $stop = $('<button>', { type: 'button', hidden: true }) .text('⏹️ நிறுத்து'); const $upload = $('<button>', { type: 'button', hidden: true }) .text('☁️ Commons-ல் பதிவேற்று'); const $skip = $('<button>', { type: 'button' }) .text('இந்தச் சொல்லைத் தவிர்'); const $cancel = $('<button>', { type: 'button' }) .text('மூடு'); buttonStyle($start, '#218838'); buttonStyle($stop, '#c82333'); buttonStyle($upload, '#1769aa'); buttonStyle($skip, '#8a6d3b'); buttonStyle($cancel, '#6c757d'); $panel.append( $('<h2>').text(`🎙️ ஒலிப்பதிவு: ${word}`), $('<p>').text( batchMode ? `வரிசை: ${index + 1} / ${words.length}` : 'ஒரு சொல்லுக்கான ஒலிப்பதிவு' ), $('<div>').css({ textAlign: 'center' }).append($start, $stop), $('<audio>', { id: 'ta-audio-preview', controls: true }).css({ display: 'none', width: '100%', margin: '10px 0' }), $('<div>', { id: 'ta-audio-status', role: 'status' }).css({ margin: '10px 0', fontWeight: 'bold', overflowWrap: 'anywhere' }), $('<div>').css({ display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between' }).append($upload, $skip, $cancel) ); $modal.append($panel); $('body').append($modal); $start.on('click', async function () { $start.prop('disabled', true); try { message('OGG பதிவு நூலகம் ஏற்றப்படுகிறது...', '#1769aa'); const OpusRecorder = await loadRecorderLibrary(); if (!navigator.mediaDevices?.getUserMedia) { throw new Error('ஒலிவாங்கிக்கு HTTPS இணைப்பு தேவை.'); } stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true } }); chunks = []; recorder = new OpusRecorder( stream, { mimeType: 'audio/ogg', audioBitsPerSecond: 64000 }, workerOptions() ); recorder.ondataavailable = event => { if (event.data?.size) chunks.push(event.data); }; recorder.onerror = event => { message('பதிவுப் பிழை: ' + errorText(event.error || event), 'red'); }; recorder.onstop = async () => { stopStream(); try { const mime = ( recorder?.mimeType || chunks[0]?.type || '' ).toLowerCase(); if (!mime.includes('ogg')) { throw new Error( 'உண்மையான OGG ஒலி உருவாகவில்லை: ' + mime ); } oggBlob = new Blob(chunks, { type: 'audio/ogg' }); if (!oggBlob.size) { throw new Error('ஒலிப்பதிவு காலியாக உள்ளது.'); } clearPreview(); previewURL = URL.createObjectURL(oggBlob); $('#ta-audio-preview') .attr('src', previewURL) .show(); $upload.prop('hidden', false).show(); message( `OGG பதிவு தயார் (${Math.ceil(oggBlob.size / 1024)} KB).`, 'green' ); } catch (error) { message(errorText(error), 'red'); } }; recorder.start(); $start.hide(); $stop.prop('hidden', false).show(); message('பதிவு நடைபெறுகிறது...', '#c62828'); } catch (error) { console.error(error); stopStream(); $start.prop('disabled', false); message(errorText(error), 'red'); } }); $stop.on('click', () => { if (recorder && recorder.state !== 'inactive') { recorder.stop(); } $stop.hide(); $start.prop('disabled', false).text('🔄 மீண்டும் பதிவு செய்').show(); }); $upload.on('click', () => uploadAudio(word)); $skip.on('click', () => { addHistory(word, 'skipped', 'பயனரால் தவிர்க்கப்பட்டது'); closeRecorder(); if (batchMode) nextWord(); }); $cancel.on('click', closeRecorder); inspect(word).then(check => { if (check.fileExists && check.pageHasTemplate) { message('கோப்பும் வார்ப்புருவும் ஏற்கெனவே உள்ளன. இந்தச் சொல் தவிர்க்கப்படும்.', 'green'); addHistory(word, 'already-complete'); setTimeout(() => { closeRecorder(); if (batchMode) nextWord(); }, 900); } else if (check.fileExists) { message('Commons கோப்பு ஏற்கெனவே உள்ளது. புதிய கோப்பை பதிவேற்றாமல், கீழே உள்ள இணைப்பு வசதியைப் பயன்படுத்தவும்.', '#8a6d3b'); const $link = $('<button>', { type: 'button' }) .text('ஏற்கெனவே உள்ள கோப்பை இணைக்கவும்'); buttonStyle($link, '#1769aa'); $link.on('click', async () => { try { await updatePage(word, check.name); const verified = await verify(word, check.name); if (!verified) { throw new Error('இணைப்பைச் சரிபார்க்க முடியவில்லை.'); } addHistory(word, 'linked-existing'); message('ஏற்கெனவே உள்ள கோப்பு இணைக்கப்பட்டது.', 'green'); setTimeout(() => { closeRecorder(); if (batchMode) nextWord(); }, 900); } catch (error) { message(errorText(error), 'red'); } }); $('#ta-audio-status').after($link); $start.prop('disabled', true); } }).catch(error => console.warn(error)); } function closeRecorder() { try { if (recorder && recorder.state !== 'inactive') recorder.stop(); } catch (_) {} stopStream(); clearPreview(); $('#ta-audio-recorder').remove(); recorder = null; oggBlob = null; busy = false; } async function uploadAudio(word) { if (busy) return; if (!oggBlob || oggBlob.type !== 'audio/ogg' || !oggBlob.size) { message('செல்லுபடியான OGG கோப்பு தேவை.', 'red'); return; } busy = true; const name = fileName(word); try { const commons = new mw.ForeignApi(COMMONS_API); const user = await commons.get({ action: 'query', meta: 'userinfo', format: 'json' }); if (!user.query?.userinfo || user.query.userinfo.anon !== undefined) { throw new Error('Wikimedia Commons-ல் உள்நுழையவும்.'); } if ((await getCommonsFile(name)).exists) { throw new Error( `File:${name} ஏற்கெனவே உள்ளது. புதிய கோப்பை மேலெழுதவில்லை.` ); } message('Commons-ல் பதிவேற்றப்படுகிறது...', '#1769aa'); const description = [ '== {{int:filedesc}} ==', '{{Information', `|description={{ta|${word} என்ற சொல்லின் ஒலிப்பு}}`, `|date=${new Date().toISOString().slice(0, 10)}`, '|source={{own}}', '|author=[[User:Neyakkoo|Neyakkoo]]', '}}', '', '== {{int:license-header}} ==', '{{CC-BY-SA-4.0}}', '', '[[Category:Tamil pronunciation]]' ].join('\n'); const result = await commons.upload(oggBlob, { filename: name, comment: `Tamil pronunciation audio: ${word}`, text: description, format: 'json' }); if (result?.upload?.result !== 'Success') { throw new Error(result?.error?.info || 'Commons பதிவேற்றம் தோல்வியடைந்தது.'); } message('பதிவேற்றம் வெற்றி. விக்சனரிப் பக்கத்தை இணைக்கிறது...', '#1769aa'); await updatePage(word, name); await new Promise(resolve => setTimeout(resolve, 1000)); if (!(await verify(word, name))) { throw new Error( 'கோப்பு பதிவேற்றப்பட்டது; பக்க இணைப்பை உறுதிப்படுத்த முடியவில்லை.' ); } addHistory(word, 'uploaded-verified'); message('OGG கோப்பும் பக்க இணைப்பும் சரிபார்க்கப்பட்டன.', 'green'); await new Promise(resolve => setTimeout(resolve, 800)); closeRecorder(); if (batchMode) { await nextWord(); } } catch (error) { console.error(error); message('பிழை: ' + errorText(error), 'red'); addHistory(word, 'error', errorText(error)); } finally { busy = false; } } async function updatePage(word, name) { const api = new mw.Api(); const page = await getPage(word); if (!page.exists) { throw new Error(`விக்சனரிப் பக்கம் இல்லை: ${word}`); } // Do not write the template again if it already exists. if (hasTemplate(page.text)) return; let text = page.text; const heading = /(^==\s*\{\{\s*மொழி\s*\|\s*ta\s*\}\}\s*==\s*)/m; if (heading.test(text)) { text = text.replace(heading, `$1\n${TEMPLATE}\n`); } else { text = `=={{மொழி|ta}}==\n\n${TEMPLATE}\n\n${text}`; } await api.postWithToken('csrf', { action: 'edit', title: word, text, summary: `ஒலிப்புக் கோப்பு இணைப்பு: ${name}`, format: 'json' }); } async function verify(word, name) { const [file, page] = await Promise.all([ getCommonsFile(name), getPage(word) ]); return file.exists && page.exists && hasTemplate(page.text); } async function nextWord() { if (!batchMode) return; while (index < words.length) { const word = words[index++]; try { const check = await inspect(word); if (check.fileExists && check.pageHasTemplate) { addHistory(word, 'already-complete'); continue; } openRecorder(word); return; } catch (error) { addHistory(word, 'error', errorText(error)); } } batchMode = false; notice( `வரிசை முடிந்தது. ${words.length} சொற்கள் பரிசீலிக்கப்பட்டன. Dashboard-ல் விவரங்களைப் பார்க்கவும்.`, 'green' ); renderDashboard(); } function renderDashboard() { const data = history(); return { total: data.length, uploaded: data.filter(x => x.status === 'uploaded-verified').length, linked: data.filter(x => x.status === 'linked-existing').length, complete: data.filter(x => x.status === 'already-complete').length, skipped: data.filter(x => x.status === 'skipped').length, errors: data.filter(x => x.status === 'error').length, records: data }; } function openDashboard() { $('#ta-audio-dashboard').remove(); const data = renderDashboard(); const $modal = $('<div>', { id: 'ta-audio-dashboard' }).css({ position: 'fixed', inset: 0, zIndex: 100002, background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '12px' }); const $panel = $('<div>').css({ background: '#fff', color: '#222', padding: '20px', borderRadius: '8px', width: 'min(760px,97vw)', maxHeight: '90vh', overflow: 'auto' }); $panel.append( $('<h2>').text('📊 தமிழ் விக்சனரி Dashboard'), $('<p>').text(`மொத்த வரலாற்றுப் பதிவுகள்: ${data.total}`), $('<p>').text(`பதிவேற்றம் + சரிபார்ப்பு: ${data.uploaded}`), $('<p>').text(`ஏற்கெனவே உள்ள கோப்பு இணைப்பு: ${data.linked}`), $('<p>').text(`முன்பே முழுமை: ${data.complete}`), $('<p>').text(`தவிர்க்கப்பட்டவை: ${data.skipped}`), $('<p>').text(`பிழைகள்: ${data.errors}`) ); const $table = $('<table>').css({ width: '100%', borderCollapse: 'collapse', fontSize: '13px' }); $table.append( $('<tr>').append( $('<th>').text('சொல்'), $('<th>').text('நிலை'), $('<th>').text('நேரம்'), $('<th>').text('குறிப்பு') ) ); data.records.slice(0, 100).forEach(item => { $table.append( $('<tr>').append( $('<td>').text(item.word), $('<td>').text(item.status), $('<td>').text(new Date(item.time).toLocaleString()), $('<td>').text(item.note || '') ) ); }); const $csv = $('<button>', { type: 'button' }) .text('CSV பதிவிறக்கம்'); const $clear = $('<button>', { type: 'button' }) .text('வரலாற்றை அழி'); const $close = $('<button>', { type: 'button' }) .text('மூடு'); buttonStyle($csv, '#1769aa'); buttonStyle($clear, '#a33'); buttonStyle($close, '#6c757d'); $csv.on('click', () => { const rows = [ ['word', 'file', 'status', 'time', 'note'], ...data.records.map(x => [ x.word, x.file, x.status, x.time, x.note ]) ]; const csv = rows.map(row => row.map(value => '"' + String(value || '').replace(/"/g, '""') + '"' ).join(',') ).join('\r\n'); const url = URL.createObjectURL( new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' }) ); const a = document.createElement('a'); a.href = url; a.download = 'tamil-wiktionary-audio-dashboard.csv'; a.click(); URL.revokeObjectURL(url); }); $clear.on('click', () => { if (confirm('உள்ளூர் வரலாற்றை மட்டும் அழிக்கவா? Commons கோப்புகள் அழிக்கப்படாது.')) { localStorage.removeItem(HISTORY_KEY); $modal.remove(); openDashboard(); } }); $close.on('click', () => $modal.remove()); $panel.append($table, $csv, $clear, $close); $modal.append($panel); $('body').append($modal); } function addToolbar() { if ($('#ta-audio-toolbar').length) return; const $toolbar = $('<span>', { id: 'ta-audio-toolbar' }).css({ display: 'inline-flex', flexWrap: 'wrap', marginLeft: '8px' }); const $single = $('<button>', { type: 'button' }) .text('🎙️ ஒலிப்பதிவு'); const $batch = $('<button>', { type: 'button' }) .text('📝 பல சொற்கள்'); const $category = $('<button>', { type: 'button' }) .text('📥 ஒலிக்கோப்புகளில்லை பகுப்பு'); const $dashboard = $('<button>', { type: 'button' }) .text('📊 Dashboard'); buttonStyle($single, '#b83232'); buttonStyle($batch, '#1769aa'); buttonStyle($category, '#287a46'); buttonStyle($dashboard, '#5e548e'); $single.on('click', async () => { try { const check = await inspect(pageWord); if (check.fileExists && check.pageHasTemplate) { notice('கோப்பும் வார்ப்புருவும் ஏற்கெனவே உள்ளன.', 'green'); return; } words = [pageWord]; index = 0; batchMode = false; openRecorder(pageWord); } catch (error) { notice(errorText(error), 'red'); } }); $batch.on('click', importManualWords); $category.on('click', importCategory); $dashboard.on('click', openDashboard); $toolbar.append($single, $batch, $category, $dashboard); $('#firstHeading').append($toolbar); } $(function () { addToolbar(); inspect(pageWord).then(check => { if (check.fileExists && check.pageHasTemplate) { notice( `இந்தச் சொல்லுக்கான கோப்பும் இணைப்பும் ஏற்கெனவே உள்ளன: ${check.name}`, 'green' ); } else if (check.fileExists) { notice( `Commons-ல் ${check.name} உள்ளது; பக்க இணைப்பு சரிபார்க்கப்பட வேண்டும்.`, '#8a6d3b' ); } }).catch(error => console.warn(error)); }); })(); rfdo3jab9z03r091gyg7tqv8detop84 2004703 2004701 2026-10-26T23:44:17Z Neyakkoo 16078 பயிற்சி 2004703 javascript text/javascript /* ============================================================ * Tamil Wiktionary OGG Audio Recorder & Linker — Enhanced * Features: * 1. Detect existing Commons audio for the exact word filename * 2. Verify Commons file and Wiktionary template after upload * 3. Batch recording queue for multiple Tamil words * 4. Dashboard and browser-local progress history * * Install in ta.wiktionary.org/wiki/MediaWiki:Common.js * or your personal common.js. Test before wider deployment. * ============================================================ */ (function () { 'use strict'; if (!window.mw || !window.$) return; if (mw.config.get('wgNamespaceNumber') !== 0) return; const COMMONS_API = 'https://commons.wikimedia.org/w/api.php'; const CDN = 'https://cdn.jsdelivr.net/npm/opus-media-recorder@0.8.0'; const AUDIO_TEMPLATE = '{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}'; const HISTORY_KEY = 'taWiktAudioRecorderHistoryV1'; const QUEUE_KEY = 'taWiktAudioRecorderQueueV1'; let recorder = null; let stream = null; let chunks = []; let oggBlob = null; let previewURL = null; let libraryPromise = null; let queue = []; let queueIndex = 0; let queueMode = false; let currentWord = mw.config.get('wgTitle'); let currentPageText = ''; let isBusy = false; if (!/^[\u0B80-\u0BFF\u0BE6-\u0BEF\s\-]+$/.test(currentWord)) { if (!/[\u0B80-\u0BFF]/.test(currentWord)) return; } function errorText(error) { if (!error) return 'தெரியாத பிழை'; if (error.info) return error.info; if (error.message) return error.message; return String(error); } function setStatus(message, color) { $('#ta-audio-status').text(message).css('color', color || '#333'); } function readJSON(key, fallback) { try { const value = localStorage.getItem(key); return value ? JSON.parse(value) : fallback; } catch (_) { return fallback; } } function writeJSON(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); } catch (_) {} } function getHistory() { return readJSON(HISTORY_KEY, []); } function recordHistory(word, result, fileName, note) { const history = getHistory(); history.unshift({ word: word, result: result, fileName: fileName || `Ta-${word}.ogg`, note: note || '', time: new Date().toISOString() }); writeJSON(HISTORY_KEY, history.slice(0, 1000)); renderDashboard(); } function safeFileName(word) { return `Ta-${word}.ogg`; } /* * FIX: * Tamil letters do not behave like English \w characters * for JavaScript's \b word-boundary test. * * This pattern checks the template name followed by a * parameter separator (|) or closing braces. */ function hasAudioTemplate(text) { return /\{\{\s*(?:ஒலிக்கோப்பு-தமிழ்|audio|ஒலிப்பு)(?=\s*[|}])/i.test(text || ''); } function loadExternalScript(url) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = url; script.async = true; script.onload = resolve; script.onerror = () => reject( new Error('நூலகத்தை ஏற்ற முடியவில்லை: ' + url) ); document.head.appendChild(script); }); } async function loadOggLibrary() { if (window.OpusMediaRecorder && window.OpusMediaRecorder.encoderWorker) { return window.OpusMediaRecorder; } if (libraryPromise) return libraryPromise; libraryPromise = (async () => { if (!window.OpusMediaRecorder) { await loadExternalScript(CDN + '/OpusMediaRecorder.umd.js'); } if (!window.OpusMediaRecorder) { throw new Error('OpusMediaRecorder நூலகம் கிடைக்கவில்லை.'); } if (!window.OpusMediaRecorder.encoderWorker) { await loadExternalScript(CDN + '/encoderWorker.umd.js'); } if (!window.OpusMediaRecorder.encoderWorker) { throw new Error('Encoder Worker ஏற்றப்படவில்லை.'); } return window.OpusMediaRecorder; })(); try { return await libraryPromise; } catch (error) { libraryPromise = null; throw error; } } function workerOptions() { return { OggOpusEncoderWasmPath: CDN + '/OggOpusEncoder.wasm', WebMOpusEncoderWasmPath: CDN + '/WebMOpusEncoder.wasm' }; } function stopStream() { if (stream) { stream.getTracks().forEach(track => track.stop()); stream = null; } } function clearPreview() { if (previewURL) { URL.revokeObjectURL(previewURL); previewURL = null; } } function styleButton($button, background) { $button.css({ padding: '7px 11px', margin: '3px', border: '0', borderRadius: '5px', background: background, color: '#fff', cursor: 'pointer', fontSize: '13px' }); } function addToolbarButtons() { if (!$('#ta-audio-toolbar').length) { const $toolbar = $('<span>', { id: 'ta-audio-toolbar' }).css({ display: 'inline-flex', flexWrap: 'wrap', gap: '3px', marginLeft: '8px' }); $('#firstHeading').append($toolbar); const $single = $('<button>', { type: 'button' }).text('🎙️ ஒலிப்பதிவு'); styleButton($single, '#b83232'); $single.on('click', async () => { try { const check = await inspectWord(currentWord); if (check.fileExists && check.pageHasTemplate) { showNotice( `இந்தச் சொல்லுக்கான OGG கோப்பும் பக்க இணைப்பும் ஏற்கெனவே உள்ளன: ${check.fileName}`, 'green' ); return; } if (check.fileExists) { showExistingFileAction(currentWord, check.fileName); return; } queue = [currentWord]; queueIndex = 0; queueMode = false; openRecorderModal(); } catch (error) { showNotice(errorText(error), 'red'); } }); const $batch = $('<button>', { type: 'button' }).text('📝 பல சொற்கள்'); styleButton($batch, '#1769aa'); $batch.on('click', openBatchDialog); const $dash = $('<button>', { type: 'button' }).text('📊 Dashboard'); styleButton($dash, '#5e548e'); $dash.on('click', openDashboard); $toolbar.append($single, $batch, $dash); } } function showNotice(message, color) { $('#ta-audio-notice').remove(); const $notice = $('<div>', { id: 'ta-audio-notice' }).text(message).css({ margin: '8px 0', padding: '8px 10px', border: '1px solid #ccc', borderRadius: '5px', color: color || '#333', fontSize: '13px' }); $('#firstHeading').after($notice); setTimeout(() => { $notice.fadeOut(500, () => $notice.remove()); }, 9000); } async function getPageData(word) { const api = new mw.Api(); const response = await api.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: word, formatversion: 2 }); const page = response?.query?.pages?.[0]; return { exists: !!page && !page.missing, text: page?.revisions?.[0]?.content || '' }; } async function commonsFileExists(fileName) { const api = new mw.ForeignApi(COMMONS_API); const response = await api.get({ action: 'query', titles: `File:${fileName}`, formatversion: 2, prop: 'imageinfo', iiprop: 'url|mime|size' }); const page = response?.query?.pages?.[0]; if (!page || page.missing) { return { exists: false, page: null }; } const info = page.imageinfo?.[0] || {}; return { exists: true, page: page, url: info.url || `https://commons.wikimedia.org/wiki/${encodeURIComponent('File:' + fileName)}`, mime: info.mime || '', size: info.size || 0 }; } async function inspectWord(word) { const fileName = safeFileName(word); const [pageData, fileData] = await Promise.all([ getPageData(word), commonsFileExists(fileName) ]); return { word: word, fileName: fileName, pageExists: pageData.exists, pageText: pageData.text, pageHasTemplate: hasAudioTemplate(pageData.text), fileExists: fileData.exists, fileUrl: fileData.url || '', mime: fileData.mime || '', fileSize: fileData.size || 0 }; } function showExistingFileAction(word, fileName) { $('#ta-audio-existing').remove(); const $box = $('<div>', { id: 'ta-audio-existing' }).css({ padding: '10px', margin: '8px 0', border: '1px solid #d7b45a', borderRadius: '5px', background: '#fff9e6' }); $box.append( $('<p>').text(`Commons-ல் ${fileName} ஏற்கெனவே உள்ளது.`) ); const $link = $('<a>', { href: 'https://commons.wikimedia.org/wiki/' + encodeURIComponent('File:' + fileName), target: '_blank', rel: 'noopener noreferrer' }).text('Commons கோப்பைப் பார்க்கவும்'); const $linkButton = $('<button>', { type: 'button' }).text('பக்கத்தில் ஒலிப்பு வார்ப்புருவை இணைக்கவும்'); styleButton($linkButton, '#1769aa'); $linkButton.on('click', async () => { $linkButton.prop('disabled', true).text('சரிபார்க்கிறது...'); try { const check = await inspectWord(word); if (check.pageHasTemplate) { $linkButton.text('ஏற்கெனவே இணைக்கப்பட்டுள்ளது'); recordHistory( word, 'already-linked', fileName, 'Existing file and template' ); } else { await updateWiktionary(word, fileName); const verify = await verifyWiktionaryLink(word); if (!verify.ok) { throw new Error( 'பக்க இணைப்பை உறுதிப்படுத்த முடியவில்லை. ' + 'பக்கத்தை மீண்டும் திறந்து சரிபார்க்கவும்.' ); } $linkButton.text('இணைக்கப்பட்டது ✓'); recordHistory( word, 'linked-existing', fileName, 'Existing Commons file linked' ); } } catch (error) { $linkButton.prop('disabled', false) .text('மீண்டும் முயற்சி செய்'); showNotice(errorText(error), 'red'); } }); $box.append( $('<div>').append($link, $('<br>'), $linkButton) ); $('#firstHeading').after($box); } function openBatchDialog() { $('#ta-audio-batch-dialog').remove(); const $modal = $('<div>', { id: 'ta-audio-batch-dialog' }).css({ position: 'fixed', inset: 0, zIndex: 100000, background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '15px' }); const $panel = $('<div>').css({ background: '#fff', color: '#222', padding: '20px', borderRadius: '8px', width: 'min(520px,96vw)', maxHeight: '90vh', overflow: 'auto' }); const $heading = $('<h2>').text('பல சொற்களின் ஒலிப்பதிவு'); const $help = $('<p>').text( 'ஒவ்வொரு சொல்லையும் தனித்தனி வரியில் உள்ளிடவும்.' ); const $input = $('<textarea>', { rows: 8, placeholder: 'கடல்\nமரம்\nமலர்' }).css({ width: '100%', boxSizing: 'border-box', padding: '8px' }); const $start = $('<button>', { type: 'button' }).text('தொடங்கு'); const $cancel = $('<button>', { type: 'button' }).text('ரத்து செய்'); styleButton($start, '#1769aa'); styleButton($cancel, '#6c757d'); $start.on('click', async () => { const words = [...new Set( $input.val() .split(/\r?\n/) .map(word => word.trim()) .filter(Boolean) )]; if (!words.length) { alert('குறைந்தது ஒரு சொல்லையாவது உள்ளிடவும்.'); return; } if (words.some(word => !/[\u0B80-\u0BFF]/.test(word))) { alert('தமிழ்ச் சொற்களை மட்டும் உள்ளிடவும்.'); return; } queue = words; queueIndex = 0; queueMode = true; writeJSON(QUEUE_KEY, { words: queue, index: 0, updated: new Date().toISOString() }); $modal.remove(); await moveToNextWord(); }); $cancel.on('click', () => $modal.remove()); $panel.append( $heading, $help, $input, $('<div>').append($start, $cancel) ); $modal.append($panel); $('body').append($modal); } function openRecorderModal() { $('#ta-audio-recorder-modal').remove(); const word = queue[queueIndex] || currentWord; const $modal = $('<div>', { id: 'ta-audio-recorder-modal' }).css({ position: 'fixed', inset: 0, zIndex: 100001, background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '15px' }); const $panel = $('<div>').css({ background: '#fff', color: '#222', padding: '20px', borderRadius: '9px', width: 'min(540px,96vw)', maxHeight: '90vh', overflow: 'auto' }); const $title = $('<h2>').text(`🎙️ ஒலிப்பதிவு: ${word}`); const $progress = $('<p>').text( queueMode ? `வரிசை: ${queueIndex + 1} / ${queue.length}` : 'தனிப்பட்ட ஒலிப்பதிவு' ); const $status = $('<p>', { id: 'ta-audio-status' }).text('பதிவுக்குத் தயாராக உள்ளது.'); const $start = $('<button>', { id: 'ta-audio-start', type: 'button' }).text('🎙️ பதிவு தொடங்கு'); const $stop = $('<button>', { id: 'ta-audio-stop', type: 'button' }).text('⏹ பதிவு நிறுத்து').hide(); const $upload = $('<button>', { id: 'ta-audio-upload', type: 'button' }).text('☁️ Commons-ல் பதிவேற்று').hide(); const $preview = $('<audio>', { id: 'ta-audio-preview', controls: true }).css({ width: '100%', marginTop: '10px' }).hide(); const $skip = $('<button>', { type: 'button' }).text('இந்தச் சொல்லைத் தவிர்'); const $close = $('<button>', { type: 'button' }).text('மூடு'); styleButton($start, '#b83232'); styleButton($stop, '#555'); styleButton($upload, '#218838'); styleButton($skip, '#8a6d3b'); styleButton($close, '#6c757d'); $start.on('click', startRecording); $stop.on('click', stopRecording); $upload.on('click', uploadRecording); $skip.on('click', async () => { if (isBusy) return; recordHistory( word, 'skipped', safeFileName(word), 'User skipped this word' ); await moveToNextWord(); }); $close.on('click', () => { stopStream(); clearPreview(); if (recorder && recorder.state !== 'inactive') { try { recorder.stop(); } catch (_) {} } $modal.remove(); }); $panel.append( $title, $progress, $status, $start, $stop, $preview, $('<div>').append($upload), $('<hr>'), $skip, $close ); $modal.append($panel); $('body').append($modal); inspectWord(word).then(check => { if (check.fileExists && check.pageHasTemplate) { setStatus( 'இந்தச் சொல்லுக்கான கோப்பும் வார்ப்புருவும் ஏற்கெனவே உள்ளன.', 'green' ); $start.prop('disabled', true); $upload.hide(); } else if (check.fileExists) { setStatus( 'Commons-ல் கோப்பு உள்ளது. புதிய கோப்பைப் பதிவேற்றாமல் ஏற்கெனவே உள்ள கோப்பை இணைக்கவும்.', '#8a6d3b' ); const $linkExisting = $('<button>', { type: 'button' }).text('ஏற்கெனவே உள்ள கோப்பை இணைக்கவும்'); styleButton($linkExisting, '#1769aa'); $linkExisting.on('click', async () => { try { await updateWiktionary(word, check.fileName); const verify = await verifyWiktionaryLink(word); if (!verify.ok) { throw new Error( 'பக்க இணைப்பை உறுதிப்படுத்த முடியவில்லை.' ); } recordHistory( word, 'linked-existing', check.fileName, 'Existing Commons file linked' ); setStatus('ஏற்கெனவே உள்ள கோப்பு இணைக்கப்பட்டது.', 'green'); await wait(900); await moveToNextWord(); } catch (error) { setStatus('பிழை: ' + errorText(error), 'red'); } }); $panel.append($linkExisting); } }).catch(error => { console.warn('முன் சரிபார்ப்பு:', error); }); } async function startRecording() { const $start = $('#ta-audio-start').prop('disabled', true); try { setStatus('OGG பதிவு வசதி ஏற்றப்படுகிறது...', '#1769aa'); const OpusRecorder = await loadOggLibrary(); if (!navigator.mediaDevices?.getUserMedia) { throw new Error( 'ஒலிவாங்கி வசதி கிடைக்கவில்லை. HTTPS இணைப்பைப் பயன்படுத்தவும்.' ); } stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } }); chunks = []; oggBlob = null; recorder = new OpusRecorder(stream, { mimeType: 'audio/ogg', audioBitsPerSecond: 64000 }, workerOptions()); recorder.ondataavailable = event => { if (event.data && event.data.size) { chunks.push(event.data); } }; recorder.onerror = event => { console.error(event); setStatus( 'பதிவுப் பிழை: ' + errorText(event.error || event), 'red' ); }; recorder.onstop = finishRecording; recorder.start(); $start.hide(); $('#ta-audio-stop') .prop('hidden', false) .show(); $('#ta-audio-upload') .prop('hidden', true) .hide(); setStatus( 'பதிவு நடைபெறுகிறது... சொல்லைத் தெளிவாகப் பேசவும்.', '#c62828' ); } catch (error) { console.error(error); stopStream(); setStatus( 'பதிவைத் தொடங்க முடியவில்லை: ' + errorText(error), 'red' ); $start.prop('disabled', false).show(); } } function stopRecording() { if (recorder && recorder.state !== 'inactive') { recorder.stop(); } $('#ta-audio-stop').hide(); $('#ta-audio-start') .prop('disabled', false) .text('🔄 மீண்டும் பதிவு செய்') .show(); setStatus('பதிவை நிறுத்துகிறது...', '#1769aa'); } async function finishRecording() { stopStream(); try { if (!chunks.length) { throw new Error('ஒலித் தரவு கிடைக்கவில்லை.'); } const mime = ( recorder?.mimeType || chunks[0].type || '' ).toLowerCase(); if (!mime.includes('ogg')) { throw new Error( 'OGG வடிவ ஒலி கிடைக்கவில்லை. பெறப்பட்ட வடிவம்: ' + (mime || 'தெரியவில்லை') ); } oggBlob = new Blob(chunks, { type: 'audio/ogg' }); if (!oggBlob.size) { throw new Error('ஒலிப்பதிவு காலியாக உள்ளது.'); } clearPreview(); previewURL = URL.createObjectURL(oggBlob); $('#ta-audio-preview') .attr('src', previewURL) .show(); $('#ta-audio-upload') .prop('disabled', false) .prop('hidden', false) .show(); setStatus( `OGG பதிவு தயார் (${Math.ceil(oggBlob.size / 1024)} KB). கேட்டுப் பார்த்து பதிவேற்றவும்.`, 'green' ); } catch (error) { console.error(error); oggBlob = null; $('#ta-audio-upload').hide(); setStatus( 'OGG பதிவு பிழை: ' + errorText(error), 'red' ); } } async function uploadRecording() { const word = queue[queueIndex] || currentWord; const fileName = safeFileName(word); if (!oggBlob || oggBlob.type !== 'audio/ogg' || !oggBlob.size) { setStatus('செல்லுபடியான OGG கோப்பு தேவை.', 'red'); return; } if (isBusy) return; isBusy = true; const $button = $('#ta-audio-upload') .prop('disabled', true) .text('பதிவேற்றப்படுகிறது...'); try { const commons = new mw.ForeignApi(COMMONS_API); const userResult = await commons.get({ action: 'query', meta: 'userinfo', format: 'json' }); if ( !userResult.query?.userinfo || userResult.query.userinfo.anon !== undefined ) { throw new Error('Wikimedia Commons-ல் உள்நுழையவும்.'); } // Check for an existing file before uploading. const precheck = await commonsFileExists(fileName); if (precheck.exists) { throw new Error( `File:${fileName} ஏற்கெனவே உள்ளது. அதை இணைக்கும் வசதியைப் பயன்படுத்தவும்.` ); } setStatus( 'Commons-ல் OGG கோப்பு பதிவேற்றப்படுகிறது...', '#1769aa' ); const description = [ '== {{int:filedesc}} ==', '{{Information', `|description={{ta|${word} என்ற சொல்லின் ஒலிப்பு}}`, `|date=${new Date().toISOString().slice(0, 10)}`, '|source={{own}}', '|author=[[User:Neyakkoo|Neyakkoo]]', '}}', '', '== {{int:license-header}} ==', '{{CC-BY-SA-4.0}}', '', '[[Category:Tamil pronunciation]]' ].join('\n'); const upload = await commons.upload(oggBlob, { filename: fileName, comment: `Tamil pronunciation audio: ${word}`, text: description, format: 'json' }); if (upload?.upload?.result !== 'Success') { throw new Error( upload?.error?.info || 'Commons பதிவேற்றம் வெற்றியடையவில்லை.' ); } setStatus( 'பதிவேற்றம் வெற்றி. கோப்பையும் விக்சனரி இணைப்பையும் சரிபார்க்கிறது...', '#1769aa' ); await wait(1200); await updateWiktionary(word, fileName); await wait(900); const verification = await verifyAll(word, fileName); if (!verification.ok) { recordHistory( word, 'uploaded-unverified', fileName, verification.message ); throw new Error( 'கோப்பு பதிவேற்றப்பட்டது; ஆனால் இணைப்பு சரிபார்ப்பு தோல்வி: ' + verification.message ); } recordHistory( word, 'uploaded-verified', fileName, 'Commons file and Wiktionary link verified' ); setStatus( 'வெற்றி! OGG கோப்பும் விக்சனரி இணைப்பும் சரிபார்க்கப்பட்டன.', 'green' ); await wait(1000); await moveToNextWord(); } catch (error) { console.error('Upload/link error:', error); setStatus('பிழை: ' + errorText(error), 'red'); $button .prop('disabled', false) .text('☁️ மீண்டும் முயற்சி செய்'); recordHistory( word, 'error', fileName, errorText(error) ); } finally { isBusy = false; } } function wait(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function updateWiktionary(word, fileName) { const api = new mw.Api(); const response = await api.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: word, formatversion: 2 }); const page = response.query?.pages?.[0]; if (!page || page.missing) { throw new Error(`விக்சனரிப் பக்கம் இல்லை: ${word}`); } const revision = page.revisions?.[0]; if (!revision) { throw new Error('பக்க உள்ளடக்கத்தைப் பெற முடியவில்லை.'); } let text = revision.content || ''; /* * FIX: * If the Tamil audio template is already present, * do not add it a second time. */ if (hasAudioTemplate(text)) { return; } const template = `${AUDIO_TEMPLATE}\n`; const languageHeading = /(^==\s*\{\{\s*மொழி\s*\|\s*ta\s*\}\}\s*==\s*)/m; if (languageHeading.test(text)) { text = text.replace( languageHeading, `$1\n${template}` ); } else { text = `=={{மொழி|ta}}==\n\n${template}\n${text}`; } await api.postWithToken('csrf', { action: 'edit', title: word, text: text, summary: `ஒலிப்புக் கோப்பு இணைப்பு: ${fileName}`, format: 'json' }); } async function verifyWiktionaryLink(word) { const page = await getPageData(word); return { ok: page.exists && hasAudioTemplate(page.text), pageText: page.text }; } async function verifyAll(word, fileName) { const [file, link] = await Promise.all([ commonsFileExists(fileName), verifyWiktionaryLink(word) ]); const ok = file.exists && link.ok; return { ok: ok, fileExists: file.exists, pageLinked: link.ok, message: !file.exists ? 'Commons கோப்பு காணப்படவில்லை.' : (!link.ok ? 'விக்சனரி பக்கத்தில் ஒலிப்பு வார்ப்புரு இல்லை.' : 'சரிபார்ப்பு வெற்றி') }; } async function moveToNextWord() { stopStream(); clearPreview(); $('#ta-audio-recorder-modal').remove(); recorder = null; oggBlob = null; isBusy = false; if (!queueMode) { renderDashboard(); return; } queueIndex += 1; writeJSON(QUEUE_KEY, { words: queue, index: queueIndex, updated: new Date().toISOString() }); while (queueIndex < queue.length) { const word = queue[queueIndex]; try { const check = await inspectWord(word); if (check.fileExists && check.pageHasTemplate) { recordHistory( word, 'already-complete', check.fileName, 'Skipped: file and template exist' ); queueIndex += 1; writeJSON(QUEUE_KEY, { words: queue, index: queueIndex, updated: new Date().toISOString() }); continue; } openRecorderModal(); return; } catch (error) { recordHistory( word, 'error', safeFileName(word), errorText(error) ); queueIndex += 1; } } queueMode = false; writeJSON(QUEUE_KEY, { words: queue, index: queueIndex, completed: true, updated: new Date().toISOString() }); showNotice( `தொடர் ஒலிப்பதிவு முடிந்தது. ${queue.length} சொற்கள் செயலாக்கப்பட்டன. Dashboard-ல் விவரங்களைப் பார்க்கவும்.`, 'green' ); renderDashboard(); } function renderDashboard() { const history = getHistory(); const today = new Date().toISOString().slice(0, 10); const uploaded = history.filter( x => x.result === 'uploaded-verified' ).length; const linked = history.filter( x => x.result === 'linked-existing' ).length; const existing = history.filter( x => x.result === 'already-complete' ).length; const skipped = history.filter( x => x.result === 'skipped' ).length; const errors = history.filter( x => x.result === 'error' || x.result === 'uploaded-unverified' ).length; const todayCount = history.filter( x => (x.time || '').startsWith(today) && ( x.result === 'uploaded-verified' || x.result === 'linked-existing' ) ).length; return { history, uploaded, linked, existing, skipped, errors, todayCount, total: history.length }; } function openDashboard() { $('#ta-audio-dashboard').remove(); const data = renderDashboard(); const $modal = $('<div>', { id: 'ta-audio-dashboard' }).css({ position: 'fixed', inset: 0, zIndex: 100002, background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '15px' }); const $panel = $('<div>').css({ background: '#fff', color: '#222', padding: '20px', borderRadius: '9px', width: 'min(760px,97vw)', maxHeight: '90vh', overflow: 'auto' }); const $grid = $('<div>').css({ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(130px,1fr))', gap: '8px' }); const cards = [ ['பதிவேற்றம் + சரிபார்ப்பு', data.uploaded, '#e7f5e8'], ['ஏற்கெனவே இருந்த கோப்பு இணைப்பு', data.linked, '#e8f1ff'], ['முன்பே முழுமை', data.existing, '#f0ebff'], ['தவிர்க்கப்பட்டது', data.skipped, '#fff4df'], ['பிழைகள்', data.errors, '#ffe8e8'], ['இன்று இணைத்தவை', data.todayCount, '#e8f7f7'] ]; cards.forEach(([label, value, bg]) => { $grid.append( $('<div>').css({ background: bg, padding: '12px', borderRadius: '7px' }).append( $('<div>').css({ fontSize: '12px' }).text(label), $('<strong>').css({ display: 'block', fontSize: '25px', marginTop: '5px' }).text(value) ) ); }); const $table = $('<table>').css({ width: '100%', borderCollapse: 'collapse', marginTop: '14px', fontSize: '13px' }); const $thead = $('<tr>'); [ 'சொல்', 'நிலை', 'நேரம்', 'குறிப்பு' ].forEach(t => { $thead.append( $('<th>').css({ textAlign: 'left', padding: '7px', borderBottom: '1px solid #ccc' }).text(t) ); }); $table.append($('<thead>').append($thead)); const $tbody = $('<tbody>'); data.history.slice(0, 100).forEach(item => { const date = item.time ? new Date(item.time).toLocaleString() : ''; const $row = $('<tr>'); [ item.word, item.result, date, item.note || '' ].forEach(value => { $row.append( $('<td>').css({ padding: '7px', borderBottom: '1px solid #eee', verticalAlign: 'top' }).text(value) ); }); $tbody.append($row); }); $table.append($tbody); const $export = $('<button>', { type: 'button' }).text('CSV அறிக்கையைப் பதிவிறக்கு'); const $clear = $('<button>', { type: 'button' }).text('உள்ளூர் வரலாற்றை அழி'); const $close = $('<button>', { type: 'button' }).text('மூடு'); styleButton($export, '#1769aa'); styleButton($clear, '#a33'); styleButton($close, '#6c757d'); $export.on('click', () => { const rows = [ ['word', 'result', 'fileName', 'time', 'note'], ...data.history.map(x => [ x.word, x.result, x.fileName, x.time, x.note ]) ]; const csv = rows.map(row => row.map(v => '"' + String(v || '').replace(/"/g, '""') + '"' ).join(',') ).join('\r\n'); const blob = new Blob( ['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' } ); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'tamil-wiktionary-audio-progress.csv'; a.click(); URL.revokeObjectURL(url); }); $clear.on('click', () => { if (confirm( 'இந்த உலாவியில் சேமிக்கப்பட்ட Dashboard வரலாற்றை அழிக்கவா? Commons கோப்புகள் அழிக்கப்படாது.' )) { writeJSON(HISTORY_KEY, []); $modal.remove(); openDashboard(); } }); $close.on('click', () => $modal.remove()); $panel.append( $('<h2>').text('📊 தமிழ் விக்சனரி ஒலிப்பதிவு Dashboard'), $('<p>').text( `இந்த உலாவியில் சேமிக்கப்பட்ட வரலாறு: ${data.total} பதிவுகள். இது உள்ளூர் Dashboard; மற்ற கணினிகள் அல்லது உலாவிகளின் வரலாறு இதில் தானாகச் சேராது.` ), $grid, $('<h3>').text('சமீபத்திய செயல்பாடுகள்'), $table, $export, $clear, $close ); $modal.append($panel); $('body').append($modal); } $(function () { addToolbarButtons(); inspectWord(currentWord).then(check => { if (check.fileExists && check.pageHasTemplate) { showNotice( `இந்தச் சொல்லுக்கான ஒலிப்புக் கோப்பும் இணைப்பும் ஏற்கெனவே உள்ளன (${check.fileName}).`, 'green' ); } else if (check.fileExists) { showNotice( `Commons-ல் ${check.fileName} உள்ளது; விக்சனரி பக்கத்தில் இணைப்பு இல்லாமல் இருக்கலாம்.`, '#8a6d3b' ); } }).catch(error => { console.warn('Audio precheck failed:', error); }); }); })(); fept04cg96j2wbw2ee5ouxcg3tpjh9c 2004705 2004703 2026-10-26T23:48:11Z Neyakkoo 16078 பயிற்சி 2004705 javascript text/javascript /* ============================================================ * Tamil Wiktionary OGG Audio Recorder & Linker — Enhanced * Features: * 1. Detect existing Commons audio for the exact word filename * 2. Verify Commons file and Wiktionary template after upload * 3. Batch recording queue for multiple Tamil words * 4. Dashboard and browser-local progress history * * Install in ta.wiktionary.org/wiki/MediaWiki:Common.js * or your personal common.js. Test before wider deployment. * ============================================================ */ (function () { 'use strict'; if (!window.mw || !window.$) return; if (mw.config.get('wgNamespaceNumber') !== 0) return; const COMMONS_API = 'https://commons.wikimedia.org/w/api.php'; const CDN = 'https://cdn.jsdelivr.net/npm/opus-media-recorder@0.8.0'; const AUDIO_TEMPLATE = '{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}'; const HISTORY_KEY = 'taWiktAudioRecorderHistoryV1'; const QUEUE_KEY = 'taWiktAudioRecorderQueueV1'; let recorder = null; let stream = null; let chunks = []; let oggBlob = null; let previewURL = null; let libraryPromise = null; let queue = []; let queueIndex = 0; let queueMode = false; let currentWord = mw.config.get('wgTitle'); let currentPageText = ''; let isBusy = false; if (!/^[\u0B80-\u0BFF\u0BE6-\u0BEF\s\-]+$/.test(currentWord)) { if (!/[\u0B80-\u0BFF]/.test(currentWord)) return; } function errorText(error) { if (!error) return 'தெரியாத பிழை'; if (error.info) return error.info; if (error.message) return error.message; return String(error); } function setStatus(message, color) { $('#ta-audio-status').text(message).css('color', color || '#333'); } function readJSON(key, fallback) { try { const value = localStorage.getItem(key); return value ? JSON.parse(value) : fallback; } catch (_) { return fallback; } } function writeJSON(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); } catch (_) {} } function getHistory() { return readJSON(HISTORY_KEY, []); } function recordHistory(word, result, fileName, note) { const history = getHistory(); history.unshift({ word: word, result: result, fileName: fileName || `Ta-${word}.ogg`, note: note || '', time: new Date().toISOString() }); writeJSON(HISTORY_KEY, history.slice(0, 1000)); renderDashboard(); } function safeFileName(word) { return `Ta-${word}.ogg`; } function hasAudioTemplate(text) { return /\{\{\s*(ஒலிக்கோப்பு-தமிழ்|audio|ஒலிப்பு)\b/i.test(text || ''); } function loadExternalScript(url) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = url; script.async = true; script.onload = resolve; script.onerror = () => reject( new Error('நூலகத்தை ஏற்ற முடியவில்லை: ' + url) ); document.head.appendChild(script); }); } async function loadOggLibrary() { if ( window.OpusMediaRecorder && window.OpusMediaRecorder.encoderWorker ) { return window.OpusMediaRecorder; } if (libraryPromise) return libraryPromise; libraryPromise = (async () => { if (!window.OpusMediaRecorder) { await loadExternalScript( CDN + '/OpusMediaRecorder.umd.js' ); } if (!window.OpusMediaRecorder) { throw new Error( 'OpusMediaRecorder நூலகம் கிடைக்கவில்லை.' ); } if (!window.OpusMediaRecorder.encoderWorker) { await loadExternalScript( CDN + '/encoderWorker.umd.js' ); } if (!window.OpusMediaRecorder.encoderWorker) { throw new Error('Encoder Worker ஏற்றப்படவில்லை.'); } return window.OpusMediaRecorder; })(); try { return await libraryPromise; } catch (error) { libraryPromise = null; throw error; } } function workerOptions() { return { OggOpusEncoderWasmPath: CDN + '/OggOpusEncoder.wasm', WebMOpusEncoderWasmPath: CDN + '/WebMOpusEncoder.wasm' }; } function stopStream() { if (stream) { stream.getTracks().forEach(track => track.stop()); stream = null; } } function clearPreview() { if (previewURL) { URL.revokeObjectURL(previewURL); previewURL = null; } } function styleButton($button, background) { $button.css({ padding: '7px 11px', margin: '3px', border: '0', borderRadius: '5px', background: background, color: '#fff', cursor: 'pointer', fontSize: '13px' }); } function addToolbarButtons() { if (!$('#ta-audio-toolbar').length) { const $toolbar = $('<span>', { id: 'ta-audio-toolbar' }).css({ display: 'inline-flex', flexWrap: 'wrap', gap: '3px', marginLeft: '8px' }); $('#firstHeading').append($toolbar); const $single = $('<button>', { type: 'button' }).text('🎙️ ஒலிப்பதிவு'); styleButton($single, '#b83232'); $single.on('click', async () => { try { const check = await inspectWord(currentWord); if (check.fileExists && check.pageHasTemplate) { showNotice( `இந்தச் சொல்லுக்கான OGG கோப்பும் பக்க இணைப்பும் ஏற்கெனவே உள்ளன: ${check.fileName}`, 'green' ); return; } if (check.fileExists) { showExistingFileAction( currentWord, check.fileName ); return; } queue = [currentWord]; queueIndex = 0; queueMode = false; openRecorderModal(); } catch (error) { showNotice(errorText(error), 'red'); } }); const $batch = $('<button>', { type: 'button' }).text('📝 பல சொற்கள்'); styleButton($batch, '#1769aa'); $batch.on('click', openBatchDialog); const $dash = $('<button>', { type: 'button' }).text('📊 Dashboard'); styleButton($dash, '#5e548e'); $dash.on('click', openDashboard); $toolbar.append($single, $batch, $dash); } } function showNotice(message, color) { $('#ta-audio-notice').remove(); const $notice = $('<div>', { id: 'ta-audio-notice' }).text(message).css({ margin: '8px 0', padding: '8px 10px', border: '1px solid #ccc', borderRadius: '5px', color: color || '#333', fontSize: '13px' }); $('#firstHeading').after($notice); setTimeout(() => { $notice.fadeOut(500, () => $notice.remove()); }, 9000); } async function getPageData(word) { const api = new mw.Api(); const response = await api.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: word, formatversion: 2 }); const page = response?.query?.pages?.[0]; return { exists: !!page && !page.missing, text: page?.revisions?.[0]?.content || '' }; } async function commonsFileExists(fileName) { const api = new mw.ForeignApi(COMMONS_API); const response = await api.get({ action: 'query', titles: `File:${fileName}`, formatversion: 2, prop: 'imageinfo', iiprop: 'url|mime|size' }); const page = response?.query?.pages?.[0]; if (!page || page.missing) { return { exists: false, page: null }; } const info = page.imageinfo?.[0] || {}; return { exists: true, page: page, url: info.url || `https://commons.wikimedia.org/wiki/${encodeURIComponent('File:' + fileName)}`, mime: info.mime || '', size: info.size || 0 }; } async function inspectWord(word) { const fileName = safeFileName(word); const [pageData, fileData] = await Promise.all([ getPageData(word), commonsFileExists(fileName) ]); return { word: word, fileName: fileName, pageExists: pageData.exists, pageText: pageData.text, pageHasTemplate: hasAudioTemplate(pageData.text), fileExists: fileData.exists, fileUrl: fileData.url || '', mime: fileData.mime || '', fileSize: fileData.size || 0 }; } function showExistingFileAction(word, fileName) { $('#ta-audio-existing').remove(); const $box = $('<div>', { id: 'ta-audio-existing' }).css({ padding: '10px', margin: '8px 0', border: '1px solid #d7b45a', borderRadius: '5px', background: '#fff9e6' }); $box.append( $('<p>').text( `Commons-ல் ${fileName} ஏற்கெனவே உள்ளது.` ) ); const $link = $('<a>', { href: 'https://commons.wikimedia.org/wiki/' + encodeURIComponent('File:' + fileName), target: '_blank', rel: 'noopener noreferrer' }).text('Commons கோப்பைப் பார்க்கவும்'); const $linkButton = $('<button>', { type: 'button' }).text('பக்கத்தில் ஒலிப்பு வார்ப்புருவை இணைக்கவும்'); styleButton($linkButton, '#1769aa'); $linkButton.on('click', async () => { $linkButton .prop('disabled', true) .text('சரிபார்க்கிறது...'); try { const check = await inspectWord(word); if (check.pageHasTemplate) { $linkButton.text('ஏற்கெனவே இணைக்கப்பட்டுள்ளது'); recordHistory( word, 'already-linked', fileName, 'Existing file and template' ); } else { await updateWiktionary(word, fileName); const verify = await verifyWiktionaryLink(word); if (!verify.ok) { throw new Error( 'பக்க இணைப்பை உறுதிப்படுத்த முடியவில்லை.' ); } $linkButton.text('இணைக்கப்பட்டது ✓'); recordHistory( word, 'linked-existing', fileName, 'Existing Commons file linked' ); } } catch (error) { $linkButton .prop('disabled', false) .text('மீண்டும் முயற்சி செய்'); showNotice(errorText(error), 'red'); } }); $box.append( $('<div>').append($link, $('<br>'), $linkButton) ); $('#firstHeading').after($box); } function openBatchDialog() { $('#ta-audio-batch-dialog').remove(); const $modal = $('<div>', { id: 'ta-audio-batch-dialog' }).css({ position: 'fixed', inset: 0, zIndex: 100000, background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '15px' }); const $panel = $('<div>').css({ background: '#fff', color: '#222', padding: '20px', borderRadius: '8px', width: 'min(520px,96vw)', maxHeight: '90vh', overflow: 'auto' }); const $textarea = $('<textarea>', { id: 'ta-audio-batch-words', rows: 8, placeholder: 'ஒவ்வொரு வரியிலும் ஒரு தமிழ் சொல் எழுதவும்\nஎ.கா.\nஅப்புதல்\nஅம்மா\nதமிழ்' }).css({ width: '100%', boxSizing: 'border-box', margin: '10px 0' }); const $start = $('<button>', { type: 'button' }).text('வரிசையைத் தொடங்கு'); const $cancel = $('<button>', { type: 'button' }).text('ரத்து செய்'); styleButton($start, '#1769aa'); styleButton($cancel, '#6c757d'); $start.on('click', () => { const words = [...new Set( $textarea.val() .split(/[\n,;]+/) .map(w => w.trim()) .filter(Boolean) )]; if (!words.length) { $('#ta-batch-error') .text('குறைந்தது ஒரு சொல்லையாவது உள்ளிடவும்.') .css('color', 'red'); return; } const invalid = words.filter( w => !/[\u0B80-\u0BFF]/.test(w) ); if (invalid.length) { $('#ta-batch-error') .text( 'தமிழ் எழுத்துகள் உள்ள சொற்களை மட்டும் உள்ளிடவும்: ' + invalid.join(', ') ) .css('color', 'red'); return; } queue = words; queueIndex = 0; queueMode = true; writeJSON(QUEUE_KEY, { words: queue, index: 0, updated: new Date().toISOString() }); $modal.remove(); openRecorderModal(); }); $cancel.on('click', () => $modal.remove()); $panel.append( $('<h2>').text( 'பல சொற்களுக்கான தொடர்ச்சியான ஒலிப்பதிவு' ), $('<p>').text( 'ஒவ்வொரு வரியிலும் ஒரு சொல் எழுதவும். ஏற்கெனவே கோப்பு உள்ள சொற்கள் தவிர்க்கப்படலாம்.' ), $textarea, $('<div>', { id: 'ta-batch-error' }), $start, $cancel ); $modal.append($panel); $('body').append($modal); } function openRecorderModal() { $('#ta-audio-recorder-modal').remove(); chunks = []; oggBlob = null; const word = queue[queueIndex] || currentWord; const $modal = $('<div>', { id: 'ta-audio-recorder-modal' }).css({ position: 'fixed', inset: 0, zIndex: 100001, background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '15px' }); const $panel = $('<div>').css({ background: '#fff', color: '#222', padding: '20px', borderRadius: '9px', width: 'min(480px,96vw)', maxHeight: '90vh', overflow: 'auto' }); const $word = $('<h2>') .css({ textAlign: 'center', marginTop: 0 }) .text(`🎙️ ஒலிப்பதிவு: ${word}`); const $progress = $('<p>') .css({ textAlign: 'center', fontSize: '13px' }) .text( queueMode ? `சொல் ${queueIndex + 1} / ${queue.length}` : 'ஒரு சொல்லுக்கான ஒலிப்பதிவு' ); const $start = $('<button>', { id: 'ta-audio-start', type: 'button' }).text('▶️ பதிவு தொடங்கு'); const $stop = $('<button>', { id: 'ta-audio-stop', type: 'button', hidden: true }).text('⏹️ நிறுத்து'); const $audio = $('<audio>', { id: 'ta-audio-preview', controls: true }).css({ display: 'none', width: '100%', margin: '12px 0' }); const $status = $('<div>', { id: 'ta-audio-status', role: 'status' }).css({ textAlign: 'center', fontWeight: 'bold', fontSize: '13px', overflowWrap: 'anywhere' }); const $upload = $('<button>', { id: 'ta-audio-upload', type: 'button', hidden: true }).text('☁️ Commons-ல் பதிவேற்று'); const $cancel = $('<button>', { id: 'ta-audio-cancel', type: 'button' }).text('ரத்து செய்'); const $skip = $('<button>', { id: 'ta-audio-skip', type: 'button' }).text('இந்தச் சொல்லைத் தவிர்'); styleButton($start, '#218838'); styleButton($stop, '#c82333'); styleButton($upload, '#1769aa'); styleButton($cancel, '#6c757d'); styleButton($skip, '#8a6d3b'); $start.on('click', startRecording); $stop.on('click', stopRecording); $upload.on('click', uploadRecording); $cancel.on('click', () => { stopStream(); clearPreview(); $modal.remove(); if (queueMode) { writeJSON(QUEUE_KEY, { words: queue, index: queueIndex, cancelled: true, updated: new Date().toISOString() }); } }); $skip.on('click', async () => { recordHistory( word, 'skipped', safeFileName(word), 'Skipped by user' ); await moveToNextWord(); }); $panel.append( $word, $progress, $('<p>') .css({ textAlign: 'center', fontSize: '13px' }) .text('பதிவேற்றப்படும் கோப்பு OGG வடிவில் இருக்க வேண்டும்.'), $('<div>') .css({ textAlign: 'center', margin: '16px 0' }) .append($start, $stop), $audio, $status, $('<div>') .css({ display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', marginTop: '18px' }) .append($cancel, $skip, $upload) ); $modal.append($panel); $('body').append($modal); inspectWord(word).then(check => { if (check.fileExists) { setStatus( `Commons-ல் ${check.fileName} ஏற்கெனவே உள்ளது. புதிய கோப்பை உருவாக்கவில்லை.`, '#8a6d3b' ); $start.prop('disabled', true); const $linkExisting = $('<button>', { type: 'button' }).text('ஏற்கெனவே உள்ள கோப்பை இணைக்கவும்'); styleButton($linkExisting, '#1769aa'); $linkExisting.on('click', async () => { $linkExisting .prop('disabled', true) .text('இணைக்கிறது...'); try { if (!check.pageHasTemplate) { await updateWiktionary( word, check.fileName ); } const verification = await verifyAll( word, check.fileName ); if (!verification.ok) { throw new Error(verification.message); } recordHistory( word, 'linked-existing', check.fileName, 'Existing Commons file linked and verified' ); setStatus( 'ஏற்கெனவே உள்ள கோப்பு இணைக்கப்பட்டு சரிபார்க்கப்பட்டது.', 'green' ); setTimeout(moveToNextWord, 900); } catch (error) { setStatus(errorText(error), 'red'); $linkExisting .prop('disabled', false) .text('மீண்டும் முயற்சி செய்'); } }); $status.after($linkExisting); if (check.pageHasTemplate) { setStatus( 'கோப்பும் பக்க இணைப்பும் ஏற்கெனவே உள்ளன; இந்தச் சொல் தவிர்க்கப்படும்.', 'green' ); recordHistory( word, 'already-complete', check.fileName, 'File and page template already exist' ); setTimeout(moveToNextWord, 1300); } } }).catch(error => { console.warn('முன் சரிபார்ப்பு:', error); }); } async function startRecording() { const $start = $('#ta-audio-start') .prop('disabled', true); try { setStatus( 'OGG பதிவு வசதி ஏற்றப்படுகிறது...', '#1769aa' ); const OpusRecorder = await loadOggLibrary(); if (!navigator.mediaDevices?.getUserMedia) { throw new Error( 'ஒலிவாங்கி வசதி கிடைக்கவில்லை. HTTPS இணைப்பைப் பயன்படுத்தவும்.' ); } stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } }); chunks = []; oggBlob = null; recorder = new OpusRecorder( stream, { mimeType: 'audio/ogg', audioBitsPerSecond: 64000 }, workerOptions() ); recorder.ondataavailable = event => { if (event.data && event.data.size) { chunks.push(event.data); } }; recorder.onerror = event => { console.error(event); setStatus( 'பதிவுப் பிழை: ' + errorText(event.error || event), 'red' ); }; recorder.onstop = finishRecording; recorder.start(); $start.hide(); $('#ta-audio-stop') .prop('hidden', false) .show(); $('#ta-audio-upload') .prop('hidden', true) .hide(); setStatus( 'பதிவு நடைபெறுகிறது... சொல்லைத் தெளிவாகப் பேசவும்.', '#c62828' ); } catch (error) { console.error(error); stopStream(); setStatus( 'பதிவைத் தொடங்க முடியவில்லை: ' + errorText(error), 'red' ); $start.prop('disabled', false).show(); } } function stopRecording() { if (recorder && recorder.state !== 'inactive') { recorder.stop(); } $('#ta-audio-stop').hide(); $('#ta-audio-start') .prop('disabled', false) .text('🔄 மீண்டும் பதிவு செய்') .show(); setStatus('பதிவை நிறுத்துகிறது...', '#1769aa'); } async function finishRecording() { stopStream(); try { if (!chunks.length) { throw new Error('ஒலித் தரவு கிடைக்கவில்லை.'); } const mime = ( recorder?.mimeType || chunks[0].type || '' ).toLowerCase(); if (!mime.includes('ogg')) { throw new Error( 'OGG வடிவ ஒலி கிடைக்கவில்லை. பெறப்பட்ட வடிவம்: ' + (mime || 'தெரியவில்லை') ); } oggBlob = new Blob(chunks, { type: 'audio/ogg' }); if (!oggBlob.size) { throw new Error('ஒலிப்பதிவு காலியாக உள்ளது.'); } clearPreview(); previewURL = URL.createObjectURL(oggBlob); $('#ta-audio-preview') .attr('src', previewURL) .show(); $('#ta-audio-upload') .prop('disabled', false) .prop('hidden', false) .show(); setStatus( `OGG பதிவு தயார் (${Math.ceil(oggBlob.size / 1024)} KB). கேட்டுப் பார்த்து பதிவேற்றவும்.`, 'green' ); } catch (error) { console.error(error); oggBlob = null; $('#ta-audio-upload').hide(); setStatus( 'OGG பதிவு பிழை: ' + errorText(error), 'red' ); } } async function uploadRecording() { const word = queue[queueIndex] || currentWord; const fileName = safeFileName(word); if (!oggBlob || oggBlob.type !== 'audio/ogg' || !oggBlob.size) { setStatus('செல்லுபடியான OGG கோப்பு தேவை.', 'red'); return; } if (isBusy) return; isBusy = true; const $button = $('#ta-audio-upload') .prop('disabled', true) .text('பதிவேற்றப்படுகிறது...'); try { const commons = new mw.ForeignApi(COMMONS_API); const userResult = await commons.get({ action: 'query', meta: 'userinfo', format: 'json' }); if ( !userResult.query?.userinfo || userResult.query.userinfo.anon !== undefined ) { throw new Error( 'Wikimedia Commons-ல் உள்நுழையவும்.' ); } const precheck = await commonsFileExists(fileName); if (precheck.exists) { throw new Error( `File:${fileName} ஏற்கெனவே உள்ளது. அதை இணைக்கும் வசதியைப் பயன்படுத்தவும்.` ); } setStatus( 'Commons-ல் OGG கோப்பு பதிவேற்றப்படுகிறது...', '#1769aa' ); const description = [ '== {{int:filedesc}} ==', '{{Information', `|description={{ta|${word} என்ற சொல்லின் ஒலிப்பு}}`, `|date=${new Date().toISOString().slice(0, 10)}`, '|source={{own}}', '|author=[[User:Neyakkoo|Neyakkoo]]', '}}', '', '== {{int:license-header}} ==', '{{CC-BY-SA-4.0}}', '', '[[Category:Tamil pronunciation]]' ].join('\n'); const upload = await commons.upload(oggBlob, { filename: fileName, comment: `Tamil pronunciation audio: ${word}`, text: description, format: 'json' }); if (upload?.upload?.result !== 'Success') { throw new Error( upload?.error?.info || 'Commons பதிவேற்றம் வெற்றியடையவில்லை.' ); } setStatus( 'பதிவேற்றம் வெற்றி. கோப்பையும் விக்சனரி இணைப்பையும் சரிபார்க்கிறது...', '#1769aa' ); await wait(1200); await updateWiktionary(word, fileName); await wait(900); const verification = await verifyAll(word, fileName); if (!verification.ok) { recordHistory( word, 'uploaded-unverified', fileName, verification.message ); throw new Error( 'கோப்பு பதிவேற்றப்பட்டது; ஆனால் இணைப்பு சரிபார்ப்பு தோல்வி: ' + verification.message ); } recordHistory( word, 'uploaded-verified', fileName, 'Commons file and Wiktionary link verified' ); setStatus( 'வெற்றி! OGG கோப்பும் விக்சனரி இணைப்பும் சரிபார்க்கப்பட்டன.', 'green' ); await wait(1000); await moveToNextWord(); } catch (error) { console.error('Upload/link error:', error); setStatus('பிழை: ' + errorText(error), 'red'); $button .prop('disabled', false) .text('☁️ மீண்டும் முயற்சி செய்'); recordHistory( word, 'error', fileName, errorText(error) ); } finally { isBusy = false; } } function wait(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function updateWiktionary(word, fileName) { const api = new mw.Api(); const response = await api.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: word, formatversion: 2 }); const page = response.query?.pages?.[0]; if (!page || page.missing) { throw new Error(`விக்சனரிப் பக்கம் இல்லை: ${word}`); } const revision = page.revisions?.[0]; if (!revision) { throw new Error('பக்க உள்ளடக்கத்தைப் பெற முடியவில்லை.'); } let text = revision.content || ''; if (hasAudioTemplate(text)) return; const template = `${AUDIO_TEMPLATE}\n`; const languageHeading = /(^==\s*\{\{\s*மொழி\s*\|\s*ta\s*\}\}\s*==\s*)/m; if (languageHeading.test(text)) { text = text.replace( languageHeading, `$1\n${template}` ); } else { text = `=={{மொழி|ta}}==\n\n${template}\n${text}`; } await api.postWithToken('csrf', { action: 'edit', title: word, text: text, summary: `ஒலிப்புக் கோப்பு இணைப்பு: ${fileName}`, format: 'json' }); } async function verifyWiktionaryLink(word) { const page = await getPageData(word); return { ok: page.exists && hasAudioTemplate(page.text), pageText: page.text }; } async function verifyAll(word, fileName) { const [file, link] = await Promise.all([ commonsFileExists(fileName), verifyWiktionaryLink(word) ]); const ok = file.exists && link.ok; return { ok: ok, fileExists: file.exists, pageLinked: link.ok, message: !file.exists ? 'Commons கோப்பு காணப்படவில்லை.' : (!link.ok ? 'விக்சனரி பக்கத்தில் ஒலிப்பு வார்ப்புரு இல்லை.' : 'சரிபார்ப்பு வெற்றி') }; } async function moveToNextWord() { stopStream(); clearPreview(); $('#ta-audio-recorder-modal').remove(); recorder = null; oggBlob = null; isBusy = false; if (!queueMode) { renderDashboard(); return; } queueIndex += 1; writeJSON(QUEUE_KEY, { words: queue, index: queueIndex, updated: new Date().toISOString() }); while (queueIndex < queue.length) { const word = queue[queueIndex]; try { const check = await inspectWord(word); if (check.fileExists && check.pageHasTemplate) { recordHistory( word, 'already-complete', check.fileName, 'Skipped: file and template exist' ); queueIndex += 1; writeJSON(QUEUE_KEY, { words: queue, index: queueIndex, updated: new Date().toISOString() }); continue; } if (check.fileExists) { openRecorderModal(); return; } openRecorderModal(); return; } catch (error) { recordHistory( word, 'error', safeFileName(word), errorText(error) ); queueIndex += 1; } } queueMode = false; writeJSON(QUEUE_KEY, { words: queue, index: queueIndex, completed: true, updated: new Date().toISOString() }); showNotice( `தொடர் ஒலிப்பதிவு முடிந்தது. ${queue.length} சொற்கள் செயலாக்கப்பட்டன. Dashboard-ல் விவரங்களைப் பார்க்கவும்.`, 'green' ); renderDashboard(); } function renderDashboard() { const history = getHistory(); const today = new Date().toISOString().slice(0, 10); const uploaded = history.filter( x => x.result === 'uploaded-verified' ).length; const linked = history.filter( x => x.result === 'linked-existing' ).length; const existing = history.filter( x => x.result === 'already-complete' ).length; const skipped = history.filter( x => x.result === 'skipped' ).length; const errors = history.filter( x => x.result === 'error' || x.result === 'uploaded-unverified' ).length; const todayCount = history.filter( x => (x.time || '').startsWith(today) && ( x.result === 'uploaded-verified' || x.result === 'linked-existing' ) ).length; return { history, uploaded, linked, existing, skipped, errors, todayCount, total: history.length }; } function openDashboard() { $('#ta-audio-dashboard').remove(); const data = renderDashboard(); const $modal = $('<div>', { id: 'ta-audio-dashboard' }).css({ position: 'fixed', inset: 0, zIndex: 100002, background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '15px' }); const $panel = $('<div>').css({ background: '#fff', color: '#222', padding: '20px', borderRadius: '9px', width: 'min(760px,97vw)', maxHeight: '90vh', overflow: 'auto' }); const $grid = $('<div>').css({ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(130px,1fr))', gap: '8px' }); const cards = [ ['பதிவேற்றம் + சரிபார்ப்பு', data.uploaded, '#e7f5e8'], ['ஏற்கெனவே இருந்த கோப்பு இணைப்பு', data.linked, '#e8f1ff'], ['முன்பே முழுமை', data.existing, '#f0ebff'], ['தவிர்க்கப்பட்டது', data.skipped, '#fff4df'], ['பிழைகள்', data.errors, '#ffe8e8'], ['இன்று இணைத்தவை', data.todayCount, '#e8f7f7'] ]; cards.forEach(([label, value, bg]) => { $grid.append( $('<div>').css({ background: bg, padding: '12px', borderRadius: '7px' }).append( $('<div>').css({ fontSize: '12px' }).text(label), $('<strong>').css({ display: 'block', fontSize: '25px', marginTop: '5px' }).text(value) ) ); }); const $table = $('<table>').css({ width: '100%', borderCollapse: 'collapse', marginTop: '14px', fontSize: '13px' }); const $thead = $('<tr>'); ['சொல்', 'நிலை', 'நேரம்', 'குறிப்பு'].forEach(t => { $thead.append( $('<th>').css({ textAlign: 'left', padding: '7px', borderBottom: '1px solid #ccc' }).text(t) ); }); $table.append($('<thead>').append($thead)); const $tbody = $('<tbody>'); data.history.slice(0, 100).forEach(item => { const date = item.time ? new Date(item.time).toLocaleString() : ''; const $row = $('<tr>'); [ item.word, item.result, date, item.note || '' ].forEach(value => { $row.append( $('<td>').css({ padding: '7px', borderBottom: '1px solid #eee', verticalAlign: 'top' }).text(value) ); }); $tbody.append($row); }); $table.append($tbody); const $export = $('<button>', { type: 'button' }).text('CSV அறிக்கையைப் பதிவிறக்கு'); const $clear = $('<button>', { type: 'button' }).text('உள்ளூர் வரலாற்றை அழி'); const $close = $('<button>', { type: 'button' }).text('மூடு'); styleButton($export, '#1769aa'); styleButton($clear, '#a33'); styleButton($close, '#6c757d'); $export.on('click', () => { const rows = [ ['word', 'result', 'fileName', 'time', 'note'], ...data.history.map(x => [ x.word, x.result, x.fileName, x.time, x.note ]) ]; const csv = rows.map(row => row.map(v => '"' + String(v || '').replace(/"/g, '""') + '"' ).join(',') ).join('\r\n'); const blob = new Blob( ['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' } ); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'tamil-wiktionary-audio-progress.csv'; a.click(); URL.revokeObjectURL(url); }); $clear.on('click', () => { if (confirm( 'இந்த உலாவியில் சேமிக்கப்பட்ட Dashboard வரலாற்றை அழிக்கவா? Commons கோப்புகள் அழிக்கப்படாது.' )) { writeJSON(HISTORY_KEY, []); $modal.remove(); openDashboard(); } }); $close.on('click', () => $modal.remove()); $panel.append( $('<h2>').text('📊 தமிழ் விக்சனரி ஒலிப்பதிவு Dashboard'), $('<p>').text( `இந்த உலாவியில் சேமிக்கப்பட்ட வரலாறு: ${data.total} பதிவுகள். இது உள்ளூர் Dashboard; மற்ற கணினிகள் அல்லது உலாவிகளின் வரலாறு இதில் தானாகச் சேராது.` ), $grid, $('<h3>').text('சமீபத்திய செயல்பாடுகள்'), $table, $export, $clear, $close ); $modal.append($panel); $('body').append($modal); } $(function () { addToolbarButtons(); inspectWord(currentWord).then(check => { if (check.fileExists && check.pageHasTemplate) { showNotice( `இந்தச் சொல்லுக்கான ஒலிப்புக் கோப்பும் இணைப்பும் ஏற்கெனவே உள்ளன (${check.fileName}).`, 'green' ); } else if (check.fileExists) { showNotice( `Commons-ல் ${check.fileName} உள்ளது; விக்சனரி பக்கத்தில் இணைப்பு இல்லாமல் இருக்கலாம்.`, '#8a6d3b' ); } }).catch(error => { console.warn('Audio precheck failed:', error); }); }); })(); 5ema9s4hzi0xhvekjm2mjb8ejrvkp67 2004706 2004705 2026-10-26T23:49:54Z Neyakkoo 16078 பயிற்சி 2004706 javascript text/javascript /* ============================================================ * Tamil Wiktionary OGG Audio Recorder & Linker — Enhanced * Features: * 1. Detect existing Commons audio for the exact word filename * 2. Verify Commons file and Wiktionary template after upload * 3. Batch recording queue for multiple Tamil words * 4. Dashboard and browser-local progress history * * Install in ta.wiktionary.org/wiki/MediaWiki:Common.js * or your personal common.js. Test before wider deployment. * ============================================================ */ (function () { 'use strict'; if (!window.mw || !window.$) return; if (mw.config.get('wgNamespaceNumber') !== 0) return; const COMMONS_API = 'https://commons.wikimedia.org/w/api.php'; const CDN = 'https://cdn.jsdelivr.net/npm/opus-media-recorder@0.8.0'; const AUDIO_TEMPLATE = '{{ஒலிக்கோப்பு-தமிழ்|இந்தியா}}'; const HISTORY_KEY = 'taWiktAudioRecorderHistoryV1'; const QUEUE_KEY = 'taWiktAudioRecorderQueueV1'; let recorder = null; let stream = null; let chunks = []; let oggBlob = null; let previewURL = null; let libraryPromise = null; let queue = []; let queueIndex = 0; let queueMode = false; let currentWord = mw.config.get('wgTitle'); let currentPageText = ''; let isBusy = false; if (!/^[\u0B80-\u0BFF\u0BE6-\u0BEF\s\-]+$/.test(currentWord)) { if (!/[\u0B80-\u0BFF]/.test(currentWord)) return; } function errorText(error) { if (!error) return 'தெரியாத பிழை'; if (error.info) return error.info; if (error.message) return error.message; return String(error); } function setStatus(message, color) { $('#ta-audio-status').text(message).css('color', color || '#333'); } function readJSON(key, fallback) { try { const value = localStorage.getItem(key); return value ? JSON.parse(value) : fallback; } catch (_) { return fallback; } } function writeJSON(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); } catch (_) {} } function getHistory() { return readJSON(HISTORY_KEY, []); } function recordHistory(word, result, fileName, note) { const history = getHistory(); history.unshift({ word: word, result: result, fileName: fileName || `Ta-${word}.ogg`, note: note || '', time: new Date().toISOString() }); writeJSON(HISTORY_KEY, history.slice(0, 1000)); renderDashboard(); } function safeFileName(word) { return `Ta-${word}.ogg`; } /* * FIX: * Tamil letters do not behave like English \w characters * for JavaScript's \b word-boundary test. * * This pattern checks the template name followed by a * parameter separator (|) or closing braces. */ function hasAudioTemplate(text) { return /\{\{\s*(?:ஒலிக்கோப்பு-தமிழ்|audio|ஒலிப்பு)(?=\s*[|}])/i.test(text || ''); } function loadExternalScript(url) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = url; script.async = true; script.onload = resolve; script.onerror = () => reject( new Error('நூலகத்தை ஏற்ற முடியவில்லை: ' + url) ); document.head.appendChild(script); }); } async function loadOggLibrary() { if (window.OpusMediaRecorder && window.OpusMediaRecorder.encoderWorker) { return window.OpusMediaRecorder; } if (libraryPromise) return libraryPromise; libraryPromise = (async () => { if (!window.OpusMediaRecorder) { await loadExternalScript(CDN + '/OpusMediaRecorder.umd.js'); } if (!window.OpusMediaRecorder) { throw new Error('OpusMediaRecorder நூலகம் கிடைக்கவில்லை.'); } if (!window.OpusMediaRecorder.encoderWorker) { await loadExternalScript(CDN + '/encoderWorker.umd.js'); } if (!window.OpusMediaRecorder.encoderWorker) { throw new Error('Encoder Worker ஏற்றப்படவில்லை.'); } return window.OpusMediaRecorder; })(); try { return await libraryPromise; } catch (error) { libraryPromise = null; throw error; } } function workerOptions() { return { OggOpusEncoderWasmPath: CDN + '/OggOpusEncoder.wasm', WebMOpusEncoderWasmPath: CDN + '/WebMOpusEncoder.wasm' }; } function stopStream() { if (stream) { stream.getTracks().forEach(track => track.stop()); stream = null; } } function clearPreview() { if (previewURL) { URL.revokeObjectURL(previewURL); previewURL = null; } } function styleButton($button, background) { $button.css({ padding: '7px 11px', margin: '3px', border: '0', borderRadius: '5px', background: background, color: '#fff', cursor: 'pointer', fontSize: '13px' }); } function addToolbarButtons() { if (!$('#ta-audio-toolbar').length) { const $toolbar = $('<span>', { id: 'ta-audio-toolbar' }).css({ display: 'inline-flex', flexWrap: 'wrap', gap: '3px', marginLeft: '8px' }); $('#firstHeading').append($toolbar); const $single = $('<button>', { type: 'button' }).text('🎙️ ஒலிப்பதிவு'); styleButton($single, '#b83232'); $single.on('click', async () => { try { const check = await inspectWord(currentWord); if (check.fileExists && check.pageHasTemplate) { showNotice( `இந்தச் சொல்லுக்கான OGG கோப்பும் பக்க இணைப்பும் ஏற்கெனவே உள்ளன: ${check.fileName}`, 'green' ); return; } if (check.fileExists) { showExistingFileAction(currentWord, check.fileName); return; } queue = [currentWord]; queueIndex = 0; queueMode = false; openRecorderModal(); } catch (error) { showNotice(errorText(error), 'red'); } }); const $batch = $('<button>', { type: 'button' }).text('📝 பல சொற்கள்'); styleButton($batch, '#1769aa'); $batch.on('click', openBatchDialog); const $dash = $('<button>', { type: 'button' }).text('📊 Dashboard'); styleButton($dash, '#5e548e'); $dash.on('click', openDashboard); $toolbar.append($single, $batch, $dash); } } function showNotice(message, color) { $('#ta-audio-notice').remove(); const $notice = $('<div>', { id: 'ta-audio-notice' }).text(message).css({ margin: '8px 0', padding: '8px 10px', border: '1px solid #ccc', borderRadius: '5px', color: color || '#333', fontSize: '13px' }); $('#firstHeading').after($notice); setTimeout(() => { $notice.fadeOut(500, () => $notice.remove()); }, 9000); } async function getPageData(word) { const api = new mw.Api(); const response = await api.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: word, formatversion: 2 }); const page = response?.query?.pages?.[0]; return { exists: !!page && !page.missing, text: page?.revisions?.[0]?.content || '' }; } async function commonsFileExists(fileName) { const api = new mw.ForeignApi(COMMONS_API); const response = await api.get({ action: 'query', titles: `File:${fileName}`, formatversion: 2, prop: 'imageinfo', iiprop: 'url|mime|size' }); const page = response?.query?.pages?.[0]; if (!page || page.missing) { return { exists: false, page: null }; } const info = page.imageinfo?.[0] || {}; return { exists: true, page: page, url: info.url || `https://commons.wikimedia.org/wiki/${encodeURIComponent('File:' + fileName)}`, mime: info.mime || '', size: info.size || 0 }; } async function inspectWord(word) { const fileName = safeFileName(word); const [pageData, fileData] = await Promise.all([ getPageData(word), commonsFileExists(fileName) ]); return { word: word, fileName: fileName, pageExists: pageData.exists, pageText: pageData.text, pageHasTemplate: hasAudioTemplate(pageData.text), fileExists: fileData.exists, fileUrl: fileData.url || '', mime: fileData.mime || '', fileSize: fileData.size || 0 }; } function showExistingFileAction(word, fileName) { $('#ta-audio-existing').remove(); const $box = $('<div>', { id: 'ta-audio-existing' }).css({ padding: '10px', margin: '8px 0', border: '1px solid #d7b45a', borderRadius: '5px', background: '#fff9e6' }); $box.append( $('<p>').text(`Commons-ல் ${fileName} ஏற்கெனவே உள்ளது.`) ); const $link = $('<a>', { href: 'https://commons.wikimedia.org/wiki/' + encodeURIComponent('File:' + fileName), target: '_blank', rel: 'noopener noreferrer' }).text('Commons கோப்பைப் பார்க்கவும்'); const $linkButton = $('<button>', { type: 'button' }).text('பக்கத்தில் ஒலிப்பு வார்ப்புருவை இணைக்கவும்'); styleButton($linkButton, '#1769aa'); $linkButton.on('click', async () => { $linkButton.prop('disabled', true).text('சரிபார்க்கிறது...'); try { const check = await inspectWord(word); if (check.pageHasTemplate) { $linkButton.text('ஏற்கெனவே இணைக்கப்பட்டுள்ளது'); recordHistory( word, 'already-linked', fileName, 'Existing file and template' ); } else { await updateWiktionary(word, fileName); const verify = await verifyWiktionaryLink(word); if (!verify.ok) { throw new Error( 'பக்க இணைப்பை உறுதிப்படுத்த முடியவில்லை. ' + 'பக்கத்தை மீண்டும் திறந்து சரிபார்க்கவும்.' ); } $linkButton.text('இணைக்கப்பட்டது ✓'); recordHistory( word, 'linked-existing', fileName, 'Existing Commons file linked' ); } } catch (error) { $linkButton.prop('disabled', false) .text('மீண்டும் முயற்சி செய்'); showNotice(errorText(error), 'red'); } }); $box.append( $('<div>').append($link, $('<br>'), $linkButton) ); $('#firstHeading').after($box); } function openBatchDialog() { $('#ta-audio-batch-dialog').remove(); const $modal = $('<div>', { id: 'ta-audio-batch-dialog' }).css({ position: 'fixed', inset: 0, zIndex: 100000, background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '15px' }); const $panel = $('<div>').css({ background: '#fff', color: '#222', padding: '20px', borderRadius: '8px', width: 'min(520px,96vw)', maxHeight: '90vh', overflow: 'auto' }); const $heading = $('<h2>').text('பல சொற்களின் ஒலிப்பதிவு'); const $help = $('<p>').text( 'ஒவ்வொரு சொல்லையும் தனித்தனி வரியில் உள்ளிடவும்.' ); const $input = $('<textarea>', { rows: 8, placeholder: 'கடல்\nமரம்\nமலர்' }).css({ width: '100%', boxSizing: 'border-box', padding: '8px' }); const $start = $('<button>', { type: 'button' }).text('தொடங்கு'); const $cancel = $('<button>', { type: 'button' }).text('ரத்து செய்'); styleButton($start, '#1769aa'); styleButton($cancel, '#6c757d'); $start.on('click', async () => { const words = [...new Set( $input.val() .split(/\r?\n/) .map(word => word.trim()) .filter(Boolean) )]; if (!words.length) { alert('குறைந்தது ஒரு சொல்லையாவது உள்ளிடவும்.'); return; } if (words.some(word => !/[\u0B80-\u0BFF]/.test(word))) { alert('தமிழ்ச் சொற்களை மட்டும் உள்ளிடவும்.'); return; } queue = words; queueIndex = 0; queueMode = true; writeJSON(QUEUE_KEY, { words: queue, index: 0, updated: new Date().toISOString() }); $modal.remove(); await moveToNextWord(); }); $cancel.on('click', () => $modal.remove()); $panel.append( $heading, $help, $input, $('<div>').append($start, $cancel) ); $modal.append($panel); $('body').append($modal); } function openRecorderModal() { $('#ta-audio-recorder-modal').remove(); const word = queue[queueIndex] || currentWord; const $modal = $('<div>', { id: 'ta-audio-recorder-modal' }).css({ position: 'fixed', inset: 0, zIndex: 100001, background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '15px' }); const $panel = $('<div>').css({ background: '#fff', color: '#222', padding: '20px', borderRadius: '9px', width: 'min(540px,96vw)', maxHeight: '90vh', overflow: 'auto' }); const $title = $('<h2>').text(`🎙️ ஒலிப்பதிவு: ${word}`); const $progress = $('<p>').text( queueMode ? `வரிசை: ${queueIndex + 1} / ${queue.length}` : 'தனிப்பட்ட ஒலிப்பதிவு' ); const $status = $('<p>', { id: 'ta-audio-status' }).text('பதிவுக்குத் தயாராக உள்ளது.'); const $start = $('<button>', { id: 'ta-audio-start', type: 'button' }).text('🎙️ பதிவு தொடங்கு'); const $stop = $('<button>', { id: 'ta-audio-stop', type: 'button' }).text('⏹ பதிவு நிறுத்து').hide(); const $upload = $('<button>', { id: 'ta-audio-upload', type: 'button' }).text('☁️ Commons-ல் பதிவேற்று').hide(); const $preview = $('<audio>', { id: 'ta-audio-preview', controls: true }).css({ width: '100%', marginTop: '10px' }).hide(); const $skip = $('<button>', { type: 'button' }).text('இந்தச் சொல்லைத் தவிர்'); const $close = $('<button>', { type: 'button' }).text('மூடு'); styleButton($start, '#b83232'); styleButton($stop, '#555'); styleButton($upload, '#218838'); styleButton($skip, '#8a6d3b'); styleButton($close, '#6c757d'); $start.on('click', startRecording); $stop.on('click', stopRecording); $upload.on('click', uploadRecording); $skip.on('click', async () => { if (isBusy) return; recordHistory( word, 'skipped', safeFileName(word), 'User skipped this word' ); await moveToNextWord(); }); $close.on('click', () => { stopStream(); clearPreview(); if (recorder && recorder.state !== 'inactive') { try { recorder.stop(); } catch (_) {} } $modal.remove(); }); $panel.append( $title, $progress, $status, $start, $stop, $preview, $('<div>').append($upload), $('<hr>'), $skip, $close ); $modal.append($panel); $('body').append($modal); inspectWord(word).then(check => { if (check.fileExists && check.pageHasTemplate) { setStatus( 'இந்தச் சொல்லுக்கான கோப்பும் வார்ப்புருவும் ஏற்கெனவே உள்ளன.', 'green' ); $start.prop('disabled', true); $upload.hide(); } else if (check.fileExists) { setStatus( 'Commons-ல் கோப்பு உள்ளது. புதிய கோப்பைப் பதிவேற்றாமல் ஏற்கெனவே உள்ள கோப்பை இணைக்கவும்.', '#8a6d3b' ); const $linkExisting = $('<button>', { type: 'button' }).text('ஏற்கெனவே உள்ள கோப்பை இணைக்கவும்'); styleButton($linkExisting, '#1769aa'); $linkExisting.on('click', async () => { try { await updateWiktionary(word, check.fileName); const verify = await verifyWiktionaryLink(word); if (!verify.ok) { throw new Error( 'பக்க இணைப்பை உறுதிப்படுத்த முடியவில்லை.' ); } recordHistory( word, 'linked-existing', check.fileName, 'Existing Commons file linked' ); setStatus('ஏற்கெனவே உள்ள கோப்பு இணைக்கப்பட்டது.', 'green'); await wait(900); await moveToNextWord(); } catch (error) { setStatus('பிழை: ' + errorText(error), 'red'); } }); $panel.append($linkExisting); } }).catch(error => { console.warn('முன் சரிபார்ப்பு:', error); }); } async function startRecording() { const $start = $('#ta-audio-start').prop('disabled', true); try { setStatus('OGG பதிவு வசதி ஏற்றப்படுகிறது...', '#1769aa'); const OpusRecorder = await loadOggLibrary(); if (!navigator.mediaDevices?.getUserMedia) { throw new Error( 'ஒலிவாங்கி வசதி கிடைக்கவில்லை. HTTPS இணைப்பைப் பயன்படுத்தவும்.' ); } stream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true } }); chunks = []; oggBlob = null; recorder = new OpusRecorder(stream, { mimeType: 'audio/ogg', audioBitsPerSecond: 64000 }, workerOptions()); recorder.ondataavailable = event => { if (event.data && event.data.size) { chunks.push(event.data); } }; recorder.onerror = event => { console.error(event); setStatus( 'பதிவுப் பிழை: ' + errorText(event.error || event), 'red' ); }; recorder.onstop = finishRecording; recorder.start(); $start.hide(); $('#ta-audio-stop') .prop('hidden', false) .show(); $('#ta-audio-upload') .prop('hidden', true) .hide(); setStatus( 'பதிவு நடைபெறுகிறது... சொல்லைத் தெளிவாகப் பேசவும்.', '#c62828' ); } catch (error) { console.error(error); stopStream(); setStatus( 'பதிவைத் தொடங்க முடியவில்லை: ' + errorText(error), 'red' ); $start.prop('disabled', false).show(); } } function stopRecording() { if (recorder && recorder.state !== 'inactive') { recorder.stop(); } $('#ta-audio-stop').hide(); $('#ta-audio-start') .prop('disabled', false) .text('🔄 மீண்டும் பதிவு செய்') .show(); setStatus('பதிவை நிறுத்துகிறது...', '#1769aa'); } async function finishRecording() { stopStream(); try { if (!chunks.length) { throw new Error('ஒலித் தரவு கிடைக்கவில்லை.'); } const mime = ( recorder?.mimeType || chunks[0].type || '' ).toLowerCase(); if (!mime.includes('ogg')) { throw new Error( 'OGG வடிவ ஒலி கிடைக்கவில்லை. பெறப்பட்ட வடிவம்: ' + (mime || 'தெரியவில்லை') ); } oggBlob = new Blob(chunks, { type: 'audio/ogg' }); if (!oggBlob.size) { throw new Error('ஒலிப்பதிவு காலியாக உள்ளது.'); } clearPreview(); previewURL = URL.createObjectURL(oggBlob); $('#ta-audio-preview') .attr('src', previewURL) .show(); $('#ta-audio-upload') .prop('disabled', false) .prop('hidden', false) .show(); setStatus( `OGG பதிவு தயார் (${Math.ceil(oggBlob.size / 1024)} KB). கேட்டுப் பார்த்து பதிவேற்றவும்.`, 'green' ); } catch (error) { console.error(error); oggBlob = null; $('#ta-audio-upload').hide(); setStatus( 'OGG பதிவு பிழை: ' + errorText(error), 'red' ); } } async function uploadRecording() { const word = queue[queueIndex] || currentWord; const fileName = safeFileName(word); if (!oggBlob || oggBlob.type !== 'audio/ogg' || !oggBlob.size) { setStatus('செல்லுபடியான OGG கோப்பு தேவை.', 'red'); return; } if (isBusy) return; isBusy = true; const $button = $('#ta-audio-upload') .prop('disabled', true) .text('பதிவேற்றப்படுகிறது...'); try { const commons = new mw.ForeignApi(COMMONS_API); const userResult = await commons.get({ action: 'query', meta: 'userinfo', format: 'json' }); if ( !userResult.query?.userinfo || userResult.query.userinfo.anon !== undefined ) { throw new Error('Wikimedia Commons-ல் உள்நுழையவும்.'); } // Check for an existing file before uploading. const precheck = await commonsFileExists(fileName); if (precheck.exists) { throw new Error( `File:${fileName} ஏற்கெனவே உள்ளது. அதை இணைக்கும் வசதியைப் பயன்படுத்தவும்.` ); } setStatus( 'Commons-ல் OGG கோப்பு பதிவேற்றப்படுகிறது...', '#1769aa' ); const description = [ '== {{int:filedesc}} ==', '{{Information', `|description={{ta|${word} என்ற சொல்லின் ஒலிப்பு}}`, `|date=${new Date().toISOString().slice(0, 10)}`, '|source={{own}}', '|author=[[User:Neyakkoo|Neyakkoo]]', '}}', '', '== {{int:license-header}} ==', '{{CC-BY-SA-4.0}}', '', '[[Category:Tamil pronunciation]]' ].join('\n'); const upload = await commons.upload(oggBlob, { filename: fileName, comment: `Tamil pronunciation audio: ${word}`, text: description, format: 'json' }); if (upload?.upload?.result !== 'Success') { throw new Error( upload?.error?.info || 'Commons பதிவேற்றம் வெற்றியடையவில்லை.' ); } setStatus( 'பதிவேற்றம் வெற்றி. கோப்பையும் விக்சனரி இணைப்பையும் சரிபார்க்கிறது...', '#1769aa' ); await wait(1200); await updateWiktionary(word, fileName); await wait(900); const verification = await verifyAll(word, fileName); if (!verification.ok) { recordHistory( word, 'uploaded-unverified', fileName, verification.message ); throw new Error( 'கோப்பு பதிவேற்றப்பட்டது; ஆனால் இணைப்பு சரிபார்ப்பு தோல்வி: ' + verification.message ); } recordHistory( word, 'uploaded-verified', fileName, 'Commons file and Wiktionary link verified' ); setStatus( 'வெற்றி! OGG கோப்பும் விக்சனரி இணைப்பும் சரிபார்க்கப்பட்டன.', 'green' ); await wait(1000); await moveToNextWord(); } catch (error) { console.error('Upload/link error:', error); setStatus('பிழை: ' + errorText(error), 'red'); $button .prop('disabled', false) .text('☁️ மீண்டும் முயற்சி செய்'); recordHistory( word, 'error', fileName, errorText(error) ); } finally { isBusy = false; } } function wait(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function updateWiktionary(word, fileName) { const api = new mw.Api(); const response = await api.get({ action: 'query', prop: 'revisions', rvprop: 'content', titles: word, formatversion: 2 }); const page = response.query?.pages?.[0]; if (!page || page.missing) { throw new Error(`விக்சனரிப் பக்கம் இல்லை: ${word}`); } const revision = page.revisions?.[0]; if (!revision) { throw new Error('பக்க உள்ளடக்கத்தைப் பெற முடியவில்லை.'); } let text = revision.content || ''; /* * FIX: * If the Tamil audio template is already present, * do not add it a second time. */ if (hasAudioTemplate(text)) { return; } const template = `${AUDIO_TEMPLATE}\n`; const languageHeading = /(^==\s*\{\{\s*மொழி\s*\|\s*ta\s*\}\}\s*==\s*)/m; if (languageHeading.test(text)) { text = text.replace( languageHeading, `$1\n${template}` ); } else { text = `=={{மொழி|ta}}==\n\n${template}\n${text}`; } await api.postWithToken('csrf', { action: 'edit', title: word, text: text, summary: `ஒலிப்புக் கோப்பு இணைப்பு: ${fileName}`, format: 'json' }); } async function verifyWiktionaryLink(word) { const page = await getPageData(word); return { ok: page.exists && hasAudioTemplate(page.text), pageText: page.text }; } async function verifyAll(word, fileName) { const [file, link] = await Promise.all([ commonsFileExists(fileName), verifyWiktionaryLink(word) ]); const ok = file.exists && link.ok; return { ok: ok, fileExists: file.exists, pageLinked: link.ok, message: !file.exists ? 'Commons கோப்பு காணப்படவில்லை.' : (!link.ok ? 'விக்சனரி பக்கத்தில் ஒலிப்பு வார்ப்புரு இல்லை.' : 'சரிபார்ப்பு வெற்றி') }; } async function moveToNextWord() { stopStream(); clearPreview(); $('#ta-audio-recorder-modal').remove(); recorder = null; oggBlob = null; isBusy = false; if (!queueMode) { renderDashboard(); return; } queueIndex += 1; writeJSON(QUEUE_KEY, { words: queue, index: queueIndex, updated: new Date().toISOString() }); while (queueIndex < queue.length) { const word = queue[queueIndex]; try { const check = await inspectWord(word); if (check.fileExists && check.pageHasTemplate) { recordHistory( word, 'already-complete', check.fileName, 'Skipped: file and template exist' ); queueIndex += 1; writeJSON(QUEUE_KEY, { words: queue, index: queueIndex, updated: new Date().toISOString() }); continue; } openRecorderModal(); return; } catch (error) { recordHistory( word, 'error', safeFileName(word), errorText(error) ); queueIndex += 1; } } queueMode = false; writeJSON(QUEUE_KEY, { words: queue, index: queueIndex, completed: true, updated: new Date().toISOString() }); showNotice( `தொடர் ஒலிப்பதிவு முடிந்தது. ${queue.length} சொற்கள் செயலாக்கப்பட்டன. Dashboard-ல் விவரங்களைப் பார்க்கவும்.`, 'green' ); renderDashboard(); } function renderDashboard() { const history = getHistory(); const today = new Date().toISOString().slice(0, 10); const uploaded = history.filter( x => x.result === 'uploaded-verified' ).length; const linked = history.filter( x => x.result === 'linked-existing' ).length; const existing = history.filter( x => x.result === 'already-complete' ).length; const skipped = history.filter( x => x.result === 'skipped' ).length; const errors = history.filter( x => x.result === 'error' || x.result === 'uploaded-unverified' ).length; const todayCount = history.filter( x => (x.time || '').startsWith(today) && ( x.result === 'uploaded-verified' || x.result === 'linked-existing' ) ).length; return { history, uploaded, linked, existing, skipped, errors, todayCount, total: history.length }; } function openDashboard() { $('#ta-audio-dashboard').remove(); const data = renderDashboard(); const $modal = $('<div>', { id: 'ta-audio-dashboard' }).css({ position: 'fixed', inset: 0, zIndex: 100002, background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '15px' }); const $panel = $('<div>').css({ background: '#fff', color: '#222', padding: '20px', borderRadius: '9px', width: 'min(760px,97vw)', maxHeight: '90vh', overflow: 'auto' }); const $grid = $('<div>').css({ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(130px,1fr))', gap: '8px' }); const cards = [ ['பதிவேற்றம் + சரிபார்ப்பு', data.uploaded, '#e7f5e8'], ['ஏற்கெனவே இருந்த கோப்பு இணைப்பு', data.linked, '#e8f1ff'], ['முன்பே முழுமை', data.existing, '#f0ebff'], ['தவிர்க்கப்பட்டது', data.skipped, '#fff4df'], ['பிழைகள்', data.errors, '#ffe8e8'], ['இன்று இணைத்தவை', data.todayCount, '#e8f7f7'] ]; cards.forEach(([label, value, bg]) => { $grid.append( $('<div>').css({ background: bg, padding: '12px', borderRadius: '7px' }).append( $('<div>').css({ fontSize: '12px' }).text(label), $('<strong>').css({ display: 'block', fontSize: '25px', marginTop: '5px' }).text(value) ) ); }); const $table = $('<table>').css({ width: '100%', borderCollapse: 'collapse', marginTop: '14px', fontSize: '13px' }); const $thead = $('<tr>'); [ 'சொல்', 'நிலை', 'நேரம்', 'குறிப்பு' ].forEach(t => { $thead.append( $('<th>').css({ textAlign: 'left', padding: '7px', borderBottom: '1px solid #ccc' }).text(t) ); }); $table.append($('<thead>').append($thead)); const $tbody = $('<tbody>'); data.history.slice(0, 100).forEach(item => { const date = item.time ? new Date(item.time).toLocaleString() : ''; const $row = $('<tr>'); [ item.word, item.result, date, item.note || '' ].forEach(value => { $row.append( $('<td>').css({ padding: '7px', borderBottom: '1px solid #eee', verticalAlign: 'top' }).text(value) ); }); $tbody.append($row); }); $table.append($tbody); const $export = $('<button>', { type: 'button' }).text('CSV அறிக்கையைப் பதிவிறக்கு'); const $clear = $('<button>', { type: 'button' }).text('உள்ளூர் வரலாற்றை அழி'); const $close = $('<button>', { type: 'button' }).text('மூடு'); styleButton($export, '#1769aa'); styleButton($clear, '#a33'); styleButton($close, '#6c757d'); $export.on('click', () => { const rows = [ ['word', 'result', 'fileName', 'time', 'note'], ...data.history.map(x => [ x.word, x.result, x.fileName, x.time, x.note ]) ]; const csv = rows.map(row => row.map(v => '"' + String(v || '').replace(/"/g, '""') + '"' ).join(',') ).join('\r\n'); const blob = new Blob( ['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' } ); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'tamil-wiktionary-audio-progress.csv'; a.click(); URL.revokeObjectURL(url); }); $clear.on('click', () => { if (confirm( 'இந்த உலாவியில் சேமிக்கப்பட்ட Dashboard வரலாற்றை அழிக்கவா? Commons கோப்புகள் அழிக்கப்படாது.' )) { writeJSON(HISTORY_KEY, []); $modal.remove(); openDashboard(); } }); $close.on('click', () => $modal.remove()); $panel.append( $('<h2>').text('📊 தமிழ் விக்சனரி ஒலிப்பதிவு Dashboard'), $('<p>').text( `இந்த உலாவியில் சேமிக்கப்பட்ட வரலாறு: ${data.total} பதிவுகள். இது உள்ளூர் Dashboard; மற்ற கணினிகள் அல்லது உலாவிகளின் வரலாறு இதில் தானாகச் சேராது.` ), $grid, $('<h3>').text('சமீபத்திய செயல்பாடுகள்'), $table, $export, $clear, $close ); $modal.append($panel); $('body').append($modal); } $(function () { addToolbarButtons(); inspectWord(currentWord).then(check => { if (check.fileExists && check.pageHasTemplate) { showNotice( `இந்தச் சொல்லுக்கான ஒலிப்புக் கோப்பும் இணைப்பும் ஏற்கெனவே உள்ளன (${check.fileName}).`, 'green' ); } else if (check.fileExists) { showNotice( `Commons-ல் ${check.fileName} உள்ளது; விக்சனரி பக்கத்தில் இணைப்பு இல்லாமல் இருக்கலாம்.`, '#8a6d3b' ); } }).catch(error => { console.warn('Audio precheck failed:', error); }); }); })(); fept04cg96j2wbw2ee5ouxcg3tpjh9c